Adds Hooks API
Adds Parse.Hooks.js in src/cloud-code/Parse.Hooks.js Moves Cloud code related functions in src/cloud-code
This commit is contained in:
6
.gitignore
vendored
6
.gitignore
vendored
@@ -32,5 +32,11 @@ node_modules
|
|||||||
# WebStorm/IntelliJ
|
# WebStorm/IntelliJ
|
||||||
.idea
|
.idea
|
||||||
|
|
||||||
|
# visual studio code
|
||||||
|
.vscode
|
||||||
|
|
||||||
# Babel.js
|
# Babel.js
|
||||||
lib/
|
lib/
|
||||||
|
|
||||||
|
# cache folder
|
||||||
|
.cache
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
var httpRequest = require("../src/httpRequest"),
|
var httpRequest = require("../src/cloud-code/httpRequest"),
|
||||||
bodyParser = require('body-parser'),
|
bodyParser = require('body-parser'),
|
||||||
express = require("express");
|
express = require("express");
|
||||||
|
|
||||||
|
|||||||
@@ -786,7 +786,11 @@ describe('Parse.ACL', () => {
|
|||||||
equal(results.length, 1);
|
equal(results.length, 1);
|
||||||
var result = results[0];
|
var result = results[0];
|
||||||
ok(result);
|
ok(result);
|
||||||
|
if (!result) {
|
||||||
|
fail("should have result");
|
||||||
|
} else {
|
||||||
equal(result.id, object.id);
|
equal(result.id, object.id);
|
||||||
|
}
|
||||||
done();
|
done();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -275,7 +275,11 @@ describe('miscellaneous', function() {
|
|||||||
var objAgain = new Parse.Object('BeforeDeleteFail', {objectId: id});
|
var objAgain = new Parse.Object('BeforeDeleteFail', {objectId: id});
|
||||||
return objAgain.fetch();
|
return objAgain.fetch();
|
||||||
}).then((objAgain) => {
|
}).then((objAgain) => {
|
||||||
|
if (objAgain) {
|
||||||
expect(objAgain.get('foo')).toEqual('bar');
|
expect(objAgain.get('foo')).toEqual('bar');
|
||||||
|
} else {
|
||||||
|
fail("unable to fetch the object ", id);
|
||||||
|
}
|
||||||
done();
|
done();
|
||||||
}, (error) => {
|
}, (error) => {
|
||||||
// We should have been able to fetch the object again
|
// We should have been able to fetch the object again
|
||||||
@@ -351,6 +355,11 @@ describe('miscellaneous', function() {
|
|||||||
it('test cloud function return types', function(done) {
|
it('test cloud function return types', function(done) {
|
||||||
Parse.Cloud.run('foo').then((result) => {
|
Parse.Cloud.run('foo').then((result) => {
|
||||||
expect(result.object instanceof Parse.Object).toBeTruthy();
|
expect(result.object instanceof Parse.Object).toBeTruthy();
|
||||||
|
if (!result.object) {
|
||||||
|
fail("Unable to run foo");
|
||||||
|
done();
|
||||||
|
return;
|
||||||
|
}
|
||||||
expect(result.object.className).toEqual('Foo');
|
expect(result.object.className).toEqual('Foo');
|
||||||
expect(result.object.get('x')).toEqual(2);
|
expect(result.object.get('x')).toEqual(2);
|
||||||
var bar = result.object.get('relation');
|
var bar = result.object.get('relation');
|
||||||
@@ -381,19 +390,21 @@ describe('miscellaneous', function() {
|
|||||||
expect(results.length).toEqual(1);
|
expect(results.length).toEqual(1);
|
||||||
expect(results[0]['foo']).toEqual('bar');
|
expect(results[0]['foo']).toEqual('bar');
|
||||||
done();
|
done();
|
||||||
});
|
}).fail( err => {
|
||||||
|
fail(err);
|
||||||
|
done();
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('beforeSave', () => {
|
describe('beforeSave', () => {
|
||||||
beforeEach(done => {
|
beforeEach(done => {
|
||||||
// Make sure the required mock for all tests is unset.
|
// Make sure the required mock for all tests is unset.
|
||||||
delete Parse.Cloud.Triggers.beforeSave.GameScore;
|
Parse.Cloud._removeHook("Triggers", "beforeSave", "GameScore");
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(done => {
|
afterEach(done => {
|
||||||
// Make sure the required mock for all tests is unset.
|
// Make sure the required mock for all tests is unset.
|
||||||
delete Parse.Cloud.Triggers.beforeSave.GameScore;
|
Parse.Cloud._removeHook("Triggers", "beforeSave", "GameScore");
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -610,8 +621,8 @@ describe('miscellaneous', function() {
|
|||||||
}).then(function() {
|
}).then(function() {
|
||||||
// Make sure the checking has been triggered
|
// Make sure the checking has been triggered
|
||||||
expect(triggerTime).toBe(2);
|
expect(triggerTime).toBe(2);
|
||||||
// Clear mock afterSave
|
// Clear mock beforeSave
|
||||||
delete Parse.Cloud.Triggers.afterSave.GameScore;
|
Parse.Cloud._removeHook("Triggers", "beforeSave", "GameScore");
|
||||||
done();
|
done();
|
||||||
}, function(error) {
|
}, function(error) {
|
||||||
fail(error);
|
fail(error);
|
||||||
@@ -663,9 +674,10 @@ describe('miscellaneous', function() {
|
|||||||
// Make sure the checking has been triggered
|
// Make sure the checking has been triggered
|
||||||
expect(triggerTime).toBe(2);
|
expect(triggerTime).toBe(2);
|
||||||
// Clear mock afterSave
|
// Clear mock afterSave
|
||||||
delete Parse.Cloud.Triggers.afterSave.GameScore;
|
Parse.Cloud._removeHook("Triggers", "afterSave", "GameScore");
|
||||||
done();
|
done();
|
||||||
}, function(error) {
|
}, function(error) {
|
||||||
|
console.error(error);
|
||||||
fail(error);
|
fail(error);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
@@ -678,12 +690,12 @@ describe('miscellaneous', function() {
|
|||||||
});
|
});
|
||||||
Parse.Cloud.run('willFail').then((s) => {
|
Parse.Cloud.run('willFail').then((s) => {
|
||||||
fail('Should not have succeeded.');
|
fail('Should not have succeeded.');
|
||||||
delete Parse.Cloud.Functions['willFail'];
|
Parse.Cloud._removeHook("Functions", "willFail");
|
||||||
done();
|
done();
|
||||||
}, (e) => {
|
}, (e) => {
|
||||||
expect(e.code).toEqual(141);
|
expect(e.code).toEqual(141);
|
||||||
expect(e.message).toEqual('noway');
|
expect(e.message).toEqual('noway');
|
||||||
delete Parse.Cloud.Functions['willFail'];
|
Parse.Cloud._removeHook("Functions", "willFail");
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -712,7 +724,7 @@ describe('miscellaneous', function() {
|
|||||||
// Make sure query string params override body params
|
// Make sure query string params override body params
|
||||||
expect(res.other).toEqual('2');
|
expect(res.other).toEqual('2');
|
||||||
expect(res.foo).toEqual("bar");
|
expect(res.foo).toEqual("bar");
|
||||||
delete Parse.Cloud.Functions['echoParams'];
|
Parse.Cloud._removeHook("Functions",'echoParams');
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -726,7 +738,7 @@ describe('miscellaneous', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
Parse.Cloud.run('functionWithParameterValidation', {"success":100}).then((s) => {
|
Parse.Cloud.run('functionWithParameterValidation', {"success":100}).then((s) => {
|
||||||
delete Parse.Cloud.Functions['functionWithParameterValidation'];
|
Parse.Cloud._removeHook("Functions", "functionWithParameterValidation");
|
||||||
done();
|
done();
|
||||||
}, (e) => {
|
}, (e) => {
|
||||||
fail('Validation should not have failed.');
|
fail('Validation should not have failed.');
|
||||||
@@ -744,7 +756,7 @@ describe('miscellaneous', function() {
|
|||||||
|
|
||||||
Parse.Cloud.run('functionWithParameterValidationFailure', {"success":500}).then((s) => {
|
Parse.Cloud.run('functionWithParameterValidationFailure', {"success":500}).then((s) => {
|
||||||
fail('Validation should not have succeeded');
|
fail('Validation should not have succeeded');
|
||||||
delete Parse.Cloud.Functions['functionWithParameterValidationFailure'];
|
Parse.Cloud._removeHook("Functions", "functionWithParameterValidationFailure");
|
||||||
done();
|
done();
|
||||||
}, (e) => {
|
}, (e) => {
|
||||||
expect(e.code).toEqual(141);
|
expect(e.code).toEqual(141);
|
||||||
|
|||||||
390
spec/ParseHooks.spec.js
Normal file
390
spec/ParseHooks.spec.js
Normal file
@@ -0,0 +1,390 @@
|
|||||||
|
/* global describe, it, expect, fail, Parse */
|
||||||
|
var request = require('request');
|
||||||
|
var triggers = require('../src/triggers');
|
||||||
|
var HooksController = require('../src/Controllers/HooksController').default;
|
||||||
|
var express = require("express");
|
||||||
|
var bodyParser = require('body-parser');
|
||||||
|
// Inject the hooks API
|
||||||
|
Parse.Hooks = require("../src/cloud-code/Parse.Hooks");
|
||||||
|
|
||||||
|
var port = 12345;
|
||||||
|
var hookServerURL = "http://localhost:"+port;
|
||||||
|
|
||||||
|
var app = express();
|
||||||
|
app.use(bodyParser.json({ 'type': '*/*' }))
|
||||||
|
app.listen(12345);
|
||||||
|
|
||||||
|
|
||||||
|
describe('Hooks', () => {
|
||||||
|
|
||||||
|
it("should have some hooks registered", (done) => {
|
||||||
|
Parse.Hooks.getFunctions().then((res) => {
|
||||||
|
expect(res.constructor).toBe(Array.prototype.constructor);
|
||||||
|
done();
|
||||||
|
}, (err) => {
|
||||||
|
fail(err);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should have some triggers registered", (done) => {
|
||||||
|
Parse.Hooks.getTriggers().then( (res) => {
|
||||||
|
expect(res.constructor).toBe(Array.prototype.constructor);
|
||||||
|
done();
|
||||||
|
}, (err) => {
|
||||||
|
fail(err);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should CRUD a function registration", (done) => {
|
||||||
|
// Create
|
||||||
|
Parse.Hooks.createFunction("My-Test-Function", "http://someurl").then((res) => {
|
||||||
|
expect(res.functionName).toBe("My-Test-Function");
|
||||||
|
expect(res.url).toBe("http://someurl")
|
||||||
|
// Find
|
||||||
|
return Parse.Hooks.getFunction("My-Test-Function");
|
||||||
|
}, (err) => {
|
||||||
|
fail(err);
|
||||||
|
done();
|
||||||
|
}).then((res) => {
|
||||||
|
expect(res).not.toBe(null);
|
||||||
|
expect(res).not.toBe(undefined);
|
||||||
|
expect(res.url).toBe("http://someurl");
|
||||||
|
// delete
|
||||||
|
return Parse.Hooks.updateFunction("My-Test-Function", "http://anotherurl");
|
||||||
|
}, (err) => {
|
||||||
|
fail(err);
|
||||||
|
done();
|
||||||
|
}).then((res) => {
|
||||||
|
expect(res.functionName).toBe("My-Test-Function");
|
||||||
|
expect(res.url).toBe("http://anotherurl")
|
||||||
|
|
||||||
|
return Parse.Hooks.deleteFunction("My-Test-Function");
|
||||||
|
}, (err) => {
|
||||||
|
fail(err);
|
||||||
|
done();
|
||||||
|
}).then((res) => {
|
||||||
|
// Find again! but should be deleted
|
||||||
|
return Parse.Hooks.getFunction("My-Test-Function");
|
||||||
|
}, (err) => {
|
||||||
|
fail(err);
|
||||||
|
done();
|
||||||
|
}).then((res) => {
|
||||||
|
fail("Should not succeed")
|
||||||
|
done();
|
||||||
|
}, (err) => {
|
||||||
|
expect(err).not.toBe(null);
|
||||||
|
expect(err).not.toBe(undefined);
|
||||||
|
expect(err.code).toBe(143);
|
||||||
|
expect(err.error).toBe("no function named: My-Test-Function is defined")
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should CRUD a trigger registration", (done) => {
|
||||||
|
// Create
|
||||||
|
Parse.Hooks.createTrigger("MyClass","beforeDelete", "http://someurl").then((res) => {
|
||||||
|
expect(res.className).toBe("MyClass");
|
||||||
|
expect(res.triggerName).toBe("beforeDelete");
|
||||||
|
expect(res.url).toBe("http://someurl")
|
||||||
|
// Find
|
||||||
|
return Parse.Hooks.getTrigger("MyClass","beforeDelete");
|
||||||
|
}, (err) => {
|
||||||
|
fail(err);
|
||||||
|
done();
|
||||||
|
}).then((res) => {
|
||||||
|
expect(res).not.toBe(null);
|
||||||
|
expect(res).not.toBe(undefined);
|
||||||
|
expect(res.url).toBe("http://someurl");
|
||||||
|
// delete
|
||||||
|
return Parse.Hooks.updateTrigger("MyClass","beforeDelete", "http://anotherurl");
|
||||||
|
}, (err) => {
|
||||||
|
fail(err);
|
||||||
|
done();
|
||||||
|
}).then((res) => {
|
||||||
|
expect(res.className).toBe("MyClass");
|
||||||
|
expect(res.url).toBe("http://anotherurl")
|
||||||
|
|
||||||
|
return Parse.Hooks.deleteTrigger("MyClass","beforeDelete");
|
||||||
|
}, (err) => {
|
||||||
|
fail(err);
|
||||||
|
done();
|
||||||
|
}).then((res) => {
|
||||||
|
// Find again! but should be deleted
|
||||||
|
return Parse.Hooks.getTrigger("MyClass","beforeDelete");
|
||||||
|
}, (err) => {
|
||||||
|
fail(err);
|
||||||
|
done();
|
||||||
|
}).then(function(){
|
||||||
|
fail("should not succeed");
|
||||||
|
done();
|
||||||
|
}, (err) => {
|
||||||
|
expect(err).not.toBe(null);
|
||||||
|
expect(err).not.toBe(undefined);
|
||||||
|
expect(err.code).toBe(143);
|
||||||
|
expect(err.error).toBe("class MyClass does not exist")
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should fail to register hooks without Master Key", (done) => {
|
||||||
|
request.post(Parse.serverURL+"/hooks/functions", {
|
||||||
|
headers: {
|
||||||
|
"X-Parse-Application-Id": Parse.applicationId,
|
||||||
|
"X-Parse-REST-API-Key": Parse.restKey,
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ url: "http://hello.word", functionName: "SomeFunction"})
|
||||||
|
}, (err, res, body) => {
|
||||||
|
body = JSON.parse(body);
|
||||||
|
expect(body.error).toBe("unauthorized");
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should fail trying to create two times the same function", (done) => {
|
||||||
|
Parse.Hooks.createFunction("my_new_function", "http://url.com").then( () => {
|
||||||
|
return Parse.Hooks.createFunction("my_new_function", "http://url.com")
|
||||||
|
}, () => {
|
||||||
|
fail("should create a new function");
|
||||||
|
}).then( () => {
|
||||||
|
fail("should not be able to create the same function");
|
||||||
|
}, (err) => {
|
||||||
|
expect(err).not.toBe(undefined);
|
||||||
|
expect(err).not.toBe(null);
|
||||||
|
expect(err.code).toBe(143);
|
||||||
|
expect(err.error).toBe('function name: my_new_function already exits')
|
||||||
|
return Parse.Hooks.deleteFunction("my_new_function");
|
||||||
|
}).then(() => {
|
||||||
|
done();
|
||||||
|
}, (err) => {
|
||||||
|
fail(err);
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should fail trying to create two times the same trigger", (done) => {
|
||||||
|
Parse.Hooks.createTrigger("MyClass", "beforeSave", "http://url.com").then( () => {
|
||||||
|
return Parse.Hooks.createTrigger("MyClass", "beforeSave", "http://url.com")
|
||||||
|
}, () => {
|
||||||
|
fail("should create a new trigger");
|
||||||
|
}).then( () => {
|
||||||
|
fail("should not be able to create the same trigger");
|
||||||
|
}, (err) => {
|
||||||
|
expect(err.code).toBe(143);
|
||||||
|
expect(err.error).toBe('class MyClass already has trigger beforeSave')
|
||||||
|
return Parse.Hooks.deleteTrigger("MyClass", "beforeSave");
|
||||||
|
}).then(() => {
|
||||||
|
done();
|
||||||
|
}, (err) => {
|
||||||
|
fail(err);
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should fail trying to update a function that don't exist", (done) => {
|
||||||
|
Parse.Hooks.updateFunction("A_COOL_FUNCTION", "http://url.com").then( () => {
|
||||||
|
fail("Should not succeed")
|
||||||
|
}, (err) => {
|
||||||
|
expect(err.code).toBe(143);
|
||||||
|
expect(err.error).toBe('no function named: A_COOL_FUNCTION is defined');
|
||||||
|
return Parse.Hooks.getFunction("A_COOL_FUNCTION")
|
||||||
|
}).then( (res) => {
|
||||||
|
fail("the function should not exist");
|
||||||
|
done();
|
||||||
|
}, (err) => {
|
||||||
|
expect(err.code).toBe(143);
|
||||||
|
expect(err.error).toBe('no function named: A_COOL_FUNCTION is defined');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should fail trying to update a trigger that don't exist", (done) => {
|
||||||
|
Parse.Hooks.updateTrigger("AClassName","beforeSave", "http://url.com").then( () => {
|
||||||
|
fail("Should not succeed")
|
||||||
|
}, (err) => {
|
||||||
|
expect(err.code).toBe(143);
|
||||||
|
expect(err.error).toBe('class AClassName does not exist');
|
||||||
|
return Parse.Hooks.getTrigger("AClassName","beforeSave")
|
||||||
|
}).then( (res) => {
|
||||||
|
fail("the function should not exist");
|
||||||
|
done();
|
||||||
|
}, (err) => {
|
||||||
|
expect(err.code).toBe(143);
|
||||||
|
expect(err.error).toBe('class AClassName does not exist');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it("should fail trying to create a malformed function", (done) => {
|
||||||
|
Parse.Hooks.createFunction("MyFunction").then( (res) => {
|
||||||
|
fail(res);
|
||||||
|
}, (err) => {
|
||||||
|
expect(err.code).toBe(143);
|
||||||
|
expect(err.error).toBe("invalid hook declaration");
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should fail trying to create a malformed function (REST)", (done) => {
|
||||||
|
request.post(Parse.serverURL+"/hooks/functions", {
|
||||||
|
headers: {
|
||||||
|
"X-Parse-Application-Id": Parse.applicationId,
|
||||||
|
"X-Parse-Master-Key": Parse.masterKey,
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ functionName: "SomeFunction"})
|
||||||
|
}, (err, res, body) => {
|
||||||
|
body = JSON.parse(body);
|
||||||
|
expect(body.error).toBe("invalid hook declaration");
|
||||||
|
expect(body.code).toBe(143);
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it("should create hooks and properly preload them", (done) => {
|
||||||
|
|
||||||
|
var promises = [];
|
||||||
|
for (var i = 0; i<5; i++) {
|
||||||
|
promises.push(Parse.Hooks.createTrigger("MyClass"+i, "beforeSave", "http://url.com/beforeSave/"+i));
|
||||||
|
promises.push(Parse.Hooks.createFunction("AFunction"+i, "http://url.com/function"+i));
|
||||||
|
}
|
||||||
|
|
||||||
|
Parse.Promise.when(promises).then(function(results){
|
||||||
|
for (var i=0; i<5; i++) {
|
||||||
|
// Delete everything from memory, as the server just started
|
||||||
|
triggers.removeTrigger("beforeSave", "MyClass"+i, Parse.applicationId);
|
||||||
|
triggers.removeFunction("AFunction"+i, Parse.applicationId);
|
||||||
|
expect(triggers.getTrigger("MyClass"+i, "beforeSave", Parse.applicationId)).toBeUndefined();
|
||||||
|
expect(triggers.getFunction("AFunction"+i, Parse.applicationId)).toBeUndefined();
|
||||||
|
}
|
||||||
|
const hooksController = new HooksController(Parse.applicationId);
|
||||||
|
return hooksController.load()
|
||||||
|
}, (err) => {
|
||||||
|
console.error(err);
|
||||||
|
fail();
|
||||||
|
done();
|
||||||
|
}).then(function() {
|
||||||
|
for (var i=0; i<5; i++) {
|
||||||
|
expect(triggers.getTrigger("MyClass"+i, "beforeSave", Parse.applicationId)).not.toBeUndefined();
|
||||||
|
expect(triggers.getFunction("AFunction"+i, Parse.applicationId)).not.toBeUndefined();
|
||||||
|
}
|
||||||
|
done();
|
||||||
|
}, (err) => {
|
||||||
|
console.error(err);
|
||||||
|
fail();
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should run the function on the test server", (done) => {
|
||||||
|
|
||||||
|
app.post("/SomeFunction", function(req, res) {
|
||||||
|
res.json({success:"OK!"});
|
||||||
|
});
|
||||||
|
|
||||||
|
Parse.Hooks.createFunction("SOME_TEST_FUNCTION", hookServerURL+"/SomeFunction").then(function(){
|
||||||
|
return Parse.Cloud.run("SOME_TEST_FUNCTION")
|
||||||
|
}, (err) => {
|
||||||
|
console.error(err);
|
||||||
|
fail("Should not fail creating a function");
|
||||||
|
done();
|
||||||
|
}).then(function(res){
|
||||||
|
expect(res).toBe("OK!");
|
||||||
|
done();
|
||||||
|
}, (err) => {
|
||||||
|
console.error(err);
|
||||||
|
fail("Should not fail calling a function");
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should run the function on the test server", (done) => {
|
||||||
|
|
||||||
|
app.post("/SomeFunctionError", function(req, res) {
|
||||||
|
res.json({error: {code: 1337, error: "hacking that one!"}});
|
||||||
|
});
|
||||||
|
// The function is delete as the DB is dropped between calls
|
||||||
|
Parse.Hooks.createFunction("SOME_TEST_FUNCTION", hookServerURL+"/SomeFunctionError").then(function(){
|
||||||
|
return Parse.Cloud.run("SOME_TEST_FUNCTION")
|
||||||
|
}, (err) => {
|
||||||
|
console.error(err);
|
||||||
|
fail("Should not fail creating a function");
|
||||||
|
done();
|
||||||
|
}).then(function(res){
|
||||||
|
fail("Should not succeed calling that function");
|
||||||
|
done();
|
||||||
|
}, (err) => {
|
||||||
|
expect(err.code).toBe(141);
|
||||||
|
expect(err.message.code).toEqual(1337)
|
||||||
|
expect(err.message.error).toEqual("hacking that one!");
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it("should run the beforeSave hook on the test server", (done) => {
|
||||||
|
var triggerCount = 0;
|
||||||
|
app.post("/BeforeSaveSome", function(req, res) {
|
||||||
|
triggerCount++;
|
||||||
|
var object = req.body.object;
|
||||||
|
object.hello = "world";
|
||||||
|
// Would need parse cloud express to set much more
|
||||||
|
// But this should override the key upon return
|
||||||
|
res.json({success: {object: object}});
|
||||||
|
});
|
||||||
|
// The function is delete as the DB is dropped between calls
|
||||||
|
Parse.Hooks.createTrigger("SomeRandomObject", "beforeSave" ,hookServerURL+"/BeforeSaveSome").then(function(){
|
||||||
|
const obj = new Parse.Object("SomeRandomObject");
|
||||||
|
return obj.save();
|
||||||
|
}).then(function(res){
|
||||||
|
expect(triggerCount).toBe(1);
|
||||||
|
return res.fetch();
|
||||||
|
}).then(function(res){
|
||||||
|
expect(res.get("hello")).toEqual("world");
|
||||||
|
done();
|
||||||
|
}).fail((err) => {
|
||||||
|
console.error(err);
|
||||||
|
fail("Should not fail creating a function");
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should run the afterSave hook on the test server", (done) => {
|
||||||
|
var triggerCount = 0;
|
||||||
|
var newObjectId;
|
||||||
|
app.post("/AfterSaveSome", function(req, res) {
|
||||||
|
triggerCount++;
|
||||||
|
var obj = new Parse.Object("AnotherObject");
|
||||||
|
obj.set("foo", "bar");
|
||||||
|
obj.save().then(function(obj){
|
||||||
|
newObjectId = obj.id;
|
||||||
|
res.json({success: {}});
|
||||||
|
})
|
||||||
|
});
|
||||||
|
// The function is delete as the DB is dropped between calls
|
||||||
|
Parse.Hooks.createTrigger("SomeRandomObject", "afterSave" ,hookServerURL+"/AfterSaveSome").then(function(){
|
||||||
|
const obj = new Parse.Object("SomeRandomObject");
|
||||||
|
return obj.save();
|
||||||
|
}).then(function(res){
|
||||||
|
var promise = new Parse.Promise();
|
||||||
|
// Wait a bit here as it's an after save
|
||||||
|
setTimeout(function(){
|
||||||
|
expect(triggerCount).toBe(1);
|
||||||
|
var q = new Parse.Query("AnotherObject");
|
||||||
|
q.get(newObjectId).then(function(r){
|
||||||
|
promise.resolve(r);
|
||||||
|
});
|
||||||
|
}, 300)
|
||||||
|
return promise;
|
||||||
|
}).then(function(res){
|
||||||
|
expect(res.get("foo")).toEqual("bar");
|
||||||
|
done();
|
||||||
|
}).fail((err) => {
|
||||||
|
console.error(err);
|
||||||
|
fail("Should not fail creating a function");
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
var Parse = require('parse/node').Parse;
|
|
||||||
|
|
||||||
Parse.Cloud.define('hello', function(req, res) {
|
Parse.Cloud.define('hello', function(req, res) {
|
||||||
res.success('Hello world!');
|
res.success('Hello world!');
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
// A Config object provides information about how a specific app is
|
// A Config object provides information about how a specific app is
|
||||||
// configured.
|
// configured.
|
||||||
// mount is the URL for the root of the API; includes http, domain, etc.
|
// mount is the URL for the root of the API; includes http, domain, etc.
|
||||||
|
|
||||||
export class Config {
|
export class Config {
|
||||||
|
|
||||||
constructor(applicationId, mount) {
|
constructor(applicationId, mount) {
|
||||||
@@ -23,8 +24,8 @@ export class Config {
|
|||||||
this.fileKey = cacheInfo.fileKey;
|
this.fileKey = cacheInfo.fileKey;
|
||||||
this.facebookAppIds = cacheInfo.facebookAppIds;
|
this.facebookAppIds = cacheInfo.facebookAppIds;
|
||||||
this.enableAnonymousUsers = cacheInfo.enableAnonymousUsers;
|
this.enableAnonymousUsers = cacheInfo.enableAnonymousUsers;
|
||||||
|
|
||||||
this.database = DatabaseAdapter.getDatabaseConnection(applicationId);
|
this.database = DatabaseAdapter.getDatabaseConnection(applicationId);
|
||||||
|
this.hooksController = cacheInfo.hooksController;
|
||||||
this.filesController = cacheInfo.filesController;
|
this.filesController = cacheInfo.filesController;
|
||||||
this.pushController = cacheInfo.pushController;
|
this.pushController = cacheInfo.pushController;
|
||||||
this.loggerController = cacheInfo.loggerController;
|
this.loggerController = cacheInfo.loggerController;
|
||||||
|
|||||||
229
src/Controllers/HooksController.js
Normal file
229
src/Controllers/HooksController.js
Normal file
@@ -0,0 +1,229 @@
|
|||||||
|
var DatabaseAdapter = require('../DatabaseAdapter'),
|
||||||
|
triggers = require('../triggers');
|
||||||
|
const collection = "_Hooks";
|
||||||
|
|
||||||
|
export class HooksController {
|
||||||
|
|
||||||
|
constructor(applicationId) {
|
||||||
|
this.applicationId = applicationId;
|
||||||
|
}
|
||||||
|
|
||||||
|
database() {
|
||||||
|
return DatabaseAdapter.getDatabaseConnection(this.applicationId);
|
||||||
|
}
|
||||||
|
|
||||||
|
collection() {
|
||||||
|
if (this._collection) {
|
||||||
|
return Promise.resolve(this._collection)
|
||||||
|
}
|
||||||
|
return this.database().rawCollection(collection).then((collection) => {
|
||||||
|
this._collection = collection;
|
||||||
|
return collection;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getFunction(functionName) {
|
||||||
|
return this.getOne({functionName: functionName})
|
||||||
|
}
|
||||||
|
|
||||||
|
getFunctions() {
|
||||||
|
return this.get({functionName: { $exists: true }})
|
||||||
|
}
|
||||||
|
|
||||||
|
getTrigger(className, triggerName) {
|
||||||
|
return this.getOne({className: className, triggerName: triggerName })
|
||||||
|
}
|
||||||
|
|
||||||
|
getTriggers() {
|
||||||
|
return this.get({className: { $exists: true }, triggerName: { $exists: true }})
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteFunction(functionName) {
|
||||||
|
triggers.removeFunction(functionName, this.applicationId);
|
||||||
|
return this.delete({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);
|
||||||
|
}
|
||||||
|
|
||||||
|
getOne(query) {
|
||||||
|
return this.collection()
|
||||||
|
.then(coll => coll.findOne(query, {_id: 0}))
|
||||||
|
.then(hook => {
|
||||||
|
return hook;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
get(query) {
|
||||||
|
return this.collection()
|
||||||
|
.then(coll => coll.find(query, {_id: 0}).toArray())
|
||||||
|
.then(hooks => {
|
||||||
|
return hooks;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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 }
|
||||||
|
} 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;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
addHookToTriggers(hook) {
|
||||||
|
var wrappedFunction = wrapToHTTPRequest(hook);
|
||||||
|
wrappedFunction.url = hook.url;
|
||||||
|
if (hook.className) {
|
||||||
|
triggers.addTrigger(hook.triggerName, hook.className, wrappedFunction, this.applicationId)
|
||||||
|
} 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) {
|
||||||
|
hook = {};
|
||||||
|
hook.functionName = aHook.functionName;
|
||||||
|
hook.url = aHook.url;
|
||||||
|
} else if (aHook && aHook.className && aHook.url && aHook.triggerName && triggers.Types[aHook.triggerName]) {
|
||||||
|
hook = {};
|
||||||
|
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`);
|
||||||
|
} else {
|
||||||
|
return this.createOrUpdateHook(aHook);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} 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}`);
|
||||||
|
}
|
||||||
|
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`);
|
||||||
|
});
|
||||||
|
} 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, "invalid hook declaration");
|
||||||
|
};
|
||||||
|
|
||||||
|
load() {
|
||||||
|
return this.getHooks().then((hooks) => {
|
||||||
|
hooks = hooks || [];
|
||||||
|
hooks.forEach((hook) => {
|
||||||
|
this.addHookToTriggers(hook);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function wrapToHTTPRequest(hook) {
|
||||||
|
return function(request, response) {
|
||||||
|
var jsonBody = {};
|
||||||
|
for(var i in request) {
|
||||||
|
jsonBody[i] = request[i];
|
||||||
|
}
|
||||||
|
if (request.object) {
|
||||||
|
jsonBody.object = request.object.toJSON();
|
||||||
|
jsonBody.object.className = request.object.className;
|
||||||
|
}
|
||||||
|
if (request.original) {
|
||||||
|
jsonBody.original = request.original.toJSON();
|
||||||
|
jsonBody.original.className = request.original.className;
|
||||||
|
}
|
||||||
|
var jsonRequest = {};
|
||||||
|
jsonRequest.headers = {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
jsonRequest.body = JSON.stringify(jsonBody);
|
||||||
|
|
||||||
|
require("request").post(hook.url, jsonRequest, function(err, res, body){
|
||||||
|
var result;
|
||||||
|
if (body) {
|
||||||
|
if (typeof body == "string") {
|
||||||
|
try {
|
||||||
|
body = JSON.parse(body);
|
||||||
|
} catch(e) {
|
||||||
|
err = {error: "Malformed response", code: -1};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!err) {
|
||||||
|
result = body.success;
|
||||||
|
err = body.error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (err) {
|
||||||
|
return response.error(err);
|
||||||
|
} else {
|
||||||
|
return response.success(result);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default HooksController;
|
||||||
@@ -31,7 +31,7 @@ export default class PromiseRouter {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
route(method, path, handler) {
|
route(method, path, ...handlers) {
|
||||||
switch(method) {
|
switch(method) {
|
||||||
case 'POST':
|
case 'POST':
|
||||||
case 'GET':
|
case 'GET':
|
||||||
@@ -42,6 +42,25 @@ export default class PromiseRouter {
|
|||||||
throw 'cannot route method: ' + method;
|
throw 'cannot route method: ' + method;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let handler = handlers[0];
|
||||||
|
|
||||||
|
if (handlers.length > 1) {
|
||||||
|
const length = handlers.length;
|
||||||
|
handler = function(req) {
|
||||||
|
var next = function(i, req, res) {
|
||||||
|
if (i == length) {
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
let result = handlers[i](req);
|
||||||
|
if (!result || typeof result.then !== "function") {
|
||||||
|
result = Promise.resolve(result);
|
||||||
|
}
|
||||||
|
return result.then((res) => (next(i+1, req, res)));
|
||||||
|
}
|
||||||
|
return next(0, req);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.routes.push({
|
this.routes.push({
|
||||||
path: path,
|
path: path,
|
||||||
method: method,
|
method: method,
|
||||||
@@ -58,7 +77,6 @@ export default class PromiseRouter {
|
|||||||
if (route.method != method) {
|
if (route.method != method) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: we can only route the specific wildcards :className and
|
// NOTE: we can only route the specific wildcards :className and
|
||||||
// :objectId, and in that order.
|
// :objectId, and in that order.
|
||||||
// This is pretty hacky but I don't want to rebuild the entire
|
// This is pretty hacky but I don't want to rebuild the entire
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ RestWrite.prototype.runBeforeTrigger = function() {
|
|||||||
|
|
||||||
return Promise.resolve().then(() => {
|
return Promise.resolve().then(() => {
|
||||||
return triggers.maybeRunTrigger(
|
return triggers.maybeRunTrigger(
|
||||||
'beforeSave', this.auth, updatedObject, originalObject);
|
'beforeSave', this.auth, updatedObject, originalObject, this.config.applicationId);
|
||||||
}).then((response) => {
|
}).then((response) => {
|
||||||
if (response && response.object) {
|
if (response && response.object) {
|
||||||
this.data = response.object;
|
this.data = response.object;
|
||||||
@@ -789,7 +789,7 @@ RestWrite.prototype.runAfterTrigger = function() {
|
|||||||
originalObject = triggers.inflate(extraData, this.originalData);
|
originalObject = triggers.inflate(extraData, this.originalData);
|
||||||
}
|
}
|
||||||
|
|
||||||
triggers.maybeRunTrigger('afterSave', this.auth, inflatedObject, originalObject);
|
triggers.maybeRunTrigger('afterSave', this.auth, inflatedObject, originalObject, this.config.applicationId);
|
||||||
};
|
};
|
||||||
|
|
||||||
// A helper to figure out what location this operation happens at.
|
// A helper to figure out what location this operation happens at.
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import PromiseRouter from '../PromiseRouter';
|
|
||||||
import express from 'express';
|
import express from 'express';
|
||||||
import BodyParser from 'body-parser';
|
import BodyParser from 'body-parser';
|
||||||
import * as Middlewares from '../middlewares';
|
import * as Middlewares from '../middlewares';
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
// functions.js
|
// FunctionsRouter.js
|
||||||
|
|
||||||
var express = require('express'),
|
var express = require('express'),
|
||||||
Parse = require('parse/node').Parse;
|
Parse = require('parse/node').Parse,
|
||||||
|
triggers = require('../triggers');
|
||||||
|
|
||||||
import PromiseRouter from '../PromiseRouter';
|
import PromiseRouter from '../PromiseRouter';
|
||||||
|
|
||||||
@@ -27,17 +28,21 @@ export class FunctionsRouter extends PromiseRouter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static handleCloudFunction(req) {
|
static handleCloudFunction(req) {
|
||||||
if (Parse.Cloud.Functions[req.params.functionName]) {
|
var applicationId = req.config.applicationId;
|
||||||
|
var theFunction = triggers.getFunction(req.params.functionName, applicationId);
|
||||||
|
var theValidator = triggers.getValidator(req.params.functionName, applicationId);
|
||||||
|
if (theFunction) {
|
||||||
|
|
||||||
|
const params = Object.assign({}, req.body, req.query);
|
||||||
var request = {
|
var request = {
|
||||||
params: Object.assign({}, req.body, req.query),
|
params: params,
|
||||||
master: req.auth && req.auth.isMaster,
|
master: req.auth && req.auth.isMaster,
|
||||||
user: req.auth && req.auth.user,
|
user: req.auth && req.auth.user,
|
||||||
installationId: req.info.installationId
|
installationId: req.info.installationId
|
||||||
};
|
};
|
||||||
|
|
||||||
if (Parse.Cloud.Validators[req.params.functionName]) {
|
if (theValidator && typeof theValidator === "function") {
|
||||||
var result = Parse.Cloud.Validators[req.params.functionName](request);
|
var result = theValidator(request);
|
||||||
if (!result) {
|
if (!result) {
|
||||||
throw new Parse.Error(Parse.Error.SCRIPT_FAILED, 'Validation failed.');
|
throw new Parse.Error(Parse.Error.SCRIPT_FAILED, 'Validation failed.');
|
||||||
}
|
}
|
||||||
@@ -45,7 +50,11 @@ export class FunctionsRouter extends PromiseRouter {
|
|||||||
|
|
||||||
return new Promise(function (resolve, reject) {
|
return new Promise(function (resolve, reject) {
|
||||||
var response = FunctionsRouter.createResponseObject(resolve, reject);
|
var response = FunctionsRouter.createResponseObject(resolve, reject);
|
||||||
Parse.Cloud.Functions[req.params.functionName](request, response);
|
// Force the keys before the function calls.
|
||||||
|
Parse.applicationId = req.config.applicationId;
|
||||||
|
Parse.javascriptKey = req.config.javascriptKey;
|
||||||
|
Parse.masterKey = req.config.masterKey;
|
||||||
|
theFunction(request, response);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
throw new Parse.Error(Parse.Error.SCRIPT_FAILED, 'Invalid function.');
|
throw new Parse.Error(Parse.Error.SCRIPT_FAILED, 'Invalid function.');
|
||||||
|
|||||||
107
src/Routers/HooksRouter.js
Normal file
107
src/Routers/HooksRouter.js
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
import { Parse } from 'parse/node';
|
||||||
|
import PromiseRouter from '../PromiseRouter';
|
||||||
|
import { HooksController } from '../Controllers/HooksController';
|
||||||
|
|
||||||
|
function enforceMasterKeyAccess(req) {
|
||||||
|
if (!req.auth.isMaster) {
|
||||||
|
throw new Parse.Error(403, "unauthorized: master key is required");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class HooksRouter extends PromiseRouter {
|
||||||
|
|
||||||
|
createHook(aHook, config) {
|
||||||
|
return config.hooksController.createHook(aHook).then( (hook) => ({response: hook}));
|
||||||
|
};
|
||||||
|
|
||||||
|
updateHook(aHook, config) {
|
||||||
|
return config.hooksController.updateHook(aHook).then((hook) => ({response: hook}));
|
||||||
|
};
|
||||||
|
|
||||||
|
handlePost(req) {
|
||||||
|
return this.createHook(req.body, req.config);
|
||||||
|
};
|
||||||
|
|
||||||
|
handleGetFunctions(req) {
|
||||||
|
var hooksController = req.config.hooksController;
|
||||||
|
if (req.params.functionName) {
|
||||||
|
return hooksController.getFunction(req.params.functionName).then( (foundFunction) => {
|
||||||
|
if (!foundFunction) {
|
||||||
|
throw new Parse.Error(143, `no function named: ${req.params.functionName} is defined`);
|
||||||
|
}
|
||||||
|
return Promise.resolve({response: foundFunction});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return hooksController.getFunctions().then((functions) => {
|
||||||
|
return { response: functions || [] };
|
||||||
|
}, (err) => {
|
||||||
|
throw err;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleGetTriggers(req) {
|
||||||
|
var hooksController = req.config.hooksController;
|
||||||
|
if (req.params.className && req.params.triggerName) {
|
||||||
|
|
||||||
|
return hooksController.getTrigger(req.params.className, req.params.triggerName).then((foundTrigger) => {
|
||||||
|
if (!foundTrigger) {
|
||||||
|
throw new Parse.Error(143,`class ${req.params.className} does not exist`);
|
||||||
|
}
|
||||||
|
return Promise.resolve({response: foundTrigger});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return hooksController.getTriggers().then((triggers) => ({ response: triggers || [] }));
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDelete(req) {
|
||||||
|
var hooksController = req.config.hooksController;
|
||||||
|
if (req.params.functionName) {
|
||||||
|
return hooksController.deleteFunction(req.params.functionName).then(() => ({response: {}}))
|
||||||
|
|
||||||
|
} else if (req.params.className && req.params.triggerName) {
|
||||||
|
return hooksController.deleteTrigger(req.params.className, req.params.triggerName).then(() => ({response: {}}))
|
||||||
|
}
|
||||||
|
return Promise.resolve({response: {}});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleUpdate(req) {
|
||||||
|
var hook;
|
||||||
|
if (req.params.functionName && req.body.url) {
|
||||||
|
hook = {}
|
||||||
|
hook.functionName = req.params.functionName;
|
||||||
|
hook.url = req.body.url;
|
||||||
|
} else if (req.params.className && req.params.triggerName && req.body.url) {
|
||||||
|
hook = {}
|
||||||
|
hook.className = req.params.className;
|
||||||
|
hook.triggerName = req.params.triggerName;
|
||||||
|
hook.url = req.body.url
|
||||||
|
} else {
|
||||||
|
throw new Parse.Error(143, "invalid hook declaration");
|
||||||
|
}
|
||||||
|
return this.updateHook(hook, req.config);
|
||||||
|
}
|
||||||
|
|
||||||
|
handlePut(req) {
|
||||||
|
var body = req.body;
|
||||||
|
if (body.__op == "Delete") {
|
||||||
|
return this.handleDelete(req);
|
||||||
|
} else {
|
||||||
|
return this.handleUpdate(req);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mountRoutes() {
|
||||||
|
this.route('GET', '/hooks/functions', enforceMasterKeyAccess, this.handleGetFunctions.bind(this));
|
||||||
|
this.route('GET', '/hooks/triggers', enforceMasterKeyAccess, this.handleGetTriggers.bind(this));
|
||||||
|
this.route('GET', '/hooks/functions/:functionName', enforceMasterKeyAccess, this.handleGetFunctions.bind(this));
|
||||||
|
this.route('GET', '/hooks/triggers/:className/:triggerName', enforceMasterKeyAccess, this.handleGetTriggers.bind(this));
|
||||||
|
this.route('POST', '/hooks/functions', enforceMasterKeyAccess, this.handlePost.bind(this));
|
||||||
|
this.route('POST', '/hooks/triggers', enforceMasterKeyAccess, this.handlePost.bind(this));
|
||||||
|
this.route('PUT', '/hooks/functions/:functionName', enforceMasterKeyAccess, this.handlePut.bind(this));
|
||||||
|
this.route('PUT', '/hooks/triggers/:className/:triggerName', enforceMasterKeyAccess, this.handlePut.bind(this));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default HooksRouter;
|
||||||
51
src/cloud-code/Parse.Cloud.js
Normal file
51
src/cloud-code/Parse.Cloud.js
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
var Parse = require("parse/node");
|
||||||
|
var triggers = require("../triggers");
|
||||||
|
|
||||||
|
function validateClassNameForTriggers(className) {
|
||||||
|
const restrictedClassNames = [ '_Session' ];
|
||||||
|
if (restrictedClassNames.indexOf(className) != -1) {
|
||||||
|
throw `Triggers are not supported for ${className} class.`;
|
||||||
|
}
|
||||||
|
return className;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getClassName(parseClass) {
|
||||||
|
if (parseClass && parseClass.className) {
|
||||||
|
return validateClassNameForTriggers(parseClass.className);
|
||||||
|
}
|
||||||
|
return validateClassNameForTriggers(parseClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
var ParseCloud = {};
|
||||||
|
ParseCloud.define = function(functionName, handler, validationHandler) {
|
||||||
|
triggers.addFunction(functionName, handler, validationHandler, Parse.applicationId);
|
||||||
|
};
|
||||||
|
|
||||||
|
ParseCloud.beforeSave = function(parseClass, handler) {
|
||||||
|
var className = getClassName(parseClass);
|
||||||
|
triggers.addTrigger('beforeSave', className, handler, Parse.applicationId);
|
||||||
|
};
|
||||||
|
|
||||||
|
ParseCloud.beforeDelete = function(parseClass, handler) {
|
||||||
|
var className = getClassName(parseClass);
|
||||||
|
triggers.addTrigger('beforeDelete', className, handler, Parse.applicationId);
|
||||||
|
};
|
||||||
|
|
||||||
|
ParseCloud.afterSave = function(parseClass, handler) {
|
||||||
|
var className = getClassName(parseClass);
|
||||||
|
triggers.addTrigger('afterSave', className, handler, Parse.applicationId);
|
||||||
|
};
|
||||||
|
|
||||||
|
ParseCloud.afterDelete = function(parseClass, handler) {
|
||||||
|
var className = getClassName(parseClass);
|
||||||
|
triggers.addTrigger('afterDelete', className, handler, Parse.applicationId);
|
||||||
|
};
|
||||||
|
|
||||||
|
ParseCloud._removeHook = function(category, name, type, applicationId) {
|
||||||
|
applicationId = applicationId || Parse.applicationId;
|
||||||
|
triggers._unregister(applicationId, category, name, type);
|
||||||
|
};
|
||||||
|
|
||||||
|
ParseCloud.httpRequest = require("./httpRequest");
|
||||||
|
|
||||||
|
module.exports = ParseCloud;
|
||||||
132
src/cloud-code/Parse.Hooks.js
Normal file
132
src/cloud-code/Parse.Hooks.js
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
var request = require("request");
|
||||||
|
const send = function(method, path, body) {
|
||||||
|
|
||||||
|
var Parse = require("parse/node").Parse;
|
||||||
|
|
||||||
|
var options = {
|
||||||
|
method: method,
|
||||||
|
url: Parse.serverURL + path,
|
||||||
|
headers: {
|
||||||
|
'X-Parse-Application-Id': Parse.applicationId,
|
||||||
|
'X-Parse-Master-Key': Parse.masterKey,
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (body) {
|
||||||
|
if (typeof body == "object") {
|
||||||
|
options.body = JSON.stringify(body);
|
||||||
|
} else {
|
||||||
|
options.body = body;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var promise = new Parse.Promise();
|
||||||
|
request(options, function(err, response, body){
|
||||||
|
if (err) {
|
||||||
|
promise.reject(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
body = JSON.parse(body);
|
||||||
|
if (body.error) {
|
||||||
|
promise.reject(body);
|
||||||
|
} else {
|
||||||
|
promise.resolve(body);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
var Hooks = {};
|
||||||
|
|
||||||
|
Hooks.getFunctions = function() {
|
||||||
|
return Hooks.get("functions");
|
||||||
|
}
|
||||||
|
|
||||||
|
Hooks.getTriggers = function() {
|
||||||
|
return Hooks.get("triggers");
|
||||||
|
}
|
||||||
|
|
||||||
|
Hooks.getFunction = function(name) {
|
||||||
|
return Hooks.get("functions", name);
|
||||||
|
}
|
||||||
|
|
||||||
|
Hooks.getTrigger = function(className, triggerName) {
|
||||||
|
return Hooks.get("triggers", className, triggerName);
|
||||||
|
}
|
||||||
|
|
||||||
|
Hooks.get = function(type, functionName, triggerName) {
|
||||||
|
var url = "/hooks/"+type;
|
||||||
|
if(functionName) {
|
||||||
|
url += "/"+functionName;
|
||||||
|
if (triggerName) {
|
||||||
|
url += "/"+triggerName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return send("GET", url);
|
||||||
|
}
|
||||||
|
|
||||||
|
Hooks.createFunction = function(functionName, url) {
|
||||||
|
return Hooks.create({functionName: functionName, url: url});
|
||||||
|
}
|
||||||
|
|
||||||
|
Hooks.createTrigger = function(className, triggerName, url) {
|
||||||
|
return Hooks.create({className: className, triggerName: triggerName, url: url});
|
||||||
|
}
|
||||||
|
|
||||||
|
Hooks.create = function(hook) {
|
||||||
|
var url;
|
||||||
|
if (hook.functionName && hook.url) {
|
||||||
|
url = "/hooks/functions";
|
||||||
|
} else if (hook.className && hook.triggerName && hook.url) {
|
||||||
|
url = "/hooks/triggers";
|
||||||
|
} else {
|
||||||
|
return Promise.reject({error: 'invalid hook declaration', code: 143});
|
||||||
|
}
|
||||||
|
return send("POST", url, hook);
|
||||||
|
}
|
||||||
|
|
||||||
|
Hooks.updateFunction = function(functionName, url) {
|
||||||
|
return Hooks.update({functionName: functionName, url: url});
|
||||||
|
}
|
||||||
|
|
||||||
|
Hooks.updateTrigger = function(className, triggerName, url) {
|
||||||
|
return Hooks.update({className: className, triggerName: triggerName, url: url});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Hooks.update = function(hook) {
|
||||||
|
var url;
|
||||||
|
if (hook.functionName && hook.url) {
|
||||||
|
url = "/hooks/functions/"+hook.functionName;
|
||||||
|
delete hook.functionName;
|
||||||
|
} else if (hook.className && hook.triggerName && hook.url) {
|
||||||
|
url = "/hooks/triggers/"+hook.className+"/"+hook.triggerName;
|
||||||
|
delete hook.className;
|
||||||
|
delete hook.triggerName;
|
||||||
|
}
|
||||||
|
return send("PUT", url, hook);
|
||||||
|
}
|
||||||
|
|
||||||
|
Hooks.deleteFunction = function(functionName) {
|
||||||
|
return Hooks.delete({functionName: functionName});
|
||||||
|
}
|
||||||
|
|
||||||
|
Hooks.deleteTrigger = function(className, triggerName) {
|
||||||
|
return Hooks.delete({className: className, triggerName: triggerName});
|
||||||
|
}
|
||||||
|
|
||||||
|
Hooks.delete = function(hook) {
|
||||||
|
var url;
|
||||||
|
if (hook.functionName) {
|
||||||
|
url = "/hooks/functions/"+hook.functionName;
|
||||||
|
delete hook.functionName;
|
||||||
|
} else if (hook.className && hook.triggerName) {
|
||||||
|
url = "/hooks/triggers/"+hook.className+"/"+hook.triggerName;
|
||||||
|
delete hook.className;
|
||||||
|
delete hook.triggerName;
|
||||||
|
}
|
||||||
|
return send("PUT", url, '{ "__op": "Delete" }');
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Hooks
|
||||||
67
src/index.js
67
src/index.js
@@ -9,8 +9,7 @@ var batch = require('./batch'),
|
|||||||
express = require('express'),
|
express = require('express'),
|
||||||
middlewares = require('./middlewares'),
|
middlewares = require('./middlewares'),
|
||||||
multer = require('multer'),
|
multer = require('multer'),
|
||||||
Parse = require('parse/node').Parse,
|
Parse = require('parse/node').Parse;
|
||||||
httpRequest = require('./httpRequest');
|
|
||||||
|
|
||||||
import PromiseRouter from './PromiseRouter';
|
import PromiseRouter from './PromiseRouter';
|
||||||
import { GridStoreAdapter } from './Adapters/Files/GridStoreAdapter';
|
import { GridStoreAdapter } from './Adapters/Files/GridStoreAdapter';
|
||||||
@@ -32,10 +31,12 @@ import { IAPValidationRouter } from './Routers/IAPValidationRouter';
|
|||||||
import { PushRouter } from './Routers/PushRouter';
|
import { PushRouter } from './Routers/PushRouter';
|
||||||
import { FilesRouter } from './Routers/FilesRouter';
|
import { FilesRouter } from './Routers/FilesRouter';
|
||||||
import { LogsRouter } from './Routers/LogsRouter';
|
import { LogsRouter } from './Routers/LogsRouter';
|
||||||
|
import { HooksRouter } from './Routers/HooksRouter';
|
||||||
|
|
||||||
import { loadAdapter } from './Adapters/AdapterLoader';
|
import { loadAdapter } from './Adapters/AdapterLoader';
|
||||||
import { FileLoggerAdapter } from './Adapters/Logger/FileLoggerAdapter';
|
import { FileLoggerAdapter } from './Adapters/Logger/FileLoggerAdapter';
|
||||||
import { LoggerController } from './Controllers/LoggerController';
|
import { LoggerController } from './Controllers/LoggerController';
|
||||||
|
import { HooksController } from './Controllers/HooksController';
|
||||||
|
|
||||||
import requiredParameter from './requiredParameter';
|
import requiredParameter from './requiredParameter';
|
||||||
// Mutate the Parse object to add the Cloud Code handlers
|
// Mutate the Parse object to add the Cloud Code handlers
|
||||||
@@ -88,6 +89,11 @@ function ParseServer({
|
|||||||
serverURL = requiredParameter('You must provide a serverURL!'),
|
serverURL = requiredParameter('You must provide a serverURL!'),
|
||||||
maxUploadSize = '20mb'
|
maxUploadSize = '20mb'
|
||||||
}) {
|
}) {
|
||||||
|
|
||||||
|
// Initialize the node client SDK automatically
|
||||||
|
Parse.initialize(appId, javascriptKey || '', masterKey);
|
||||||
|
Parse.serverURL = serverURL || '';
|
||||||
|
|
||||||
if (databaseAdapter) {
|
if (databaseAdapter) {
|
||||||
DatabaseAdapter.setAdapter(databaseAdapter);
|
DatabaseAdapter.setAdapter(databaseAdapter);
|
||||||
}
|
}
|
||||||
@@ -95,6 +101,7 @@ function ParseServer({
|
|||||||
if (databaseURI) {
|
if (databaseURI) {
|
||||||
DatabaseAdapter.setAppDatabaseURI(appId, databaseURI);
|
DatabaseAdapter.setAppDatabaseURI(appId, databaseURI);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cloud) {
|
if (cloud) {
|
||||||
addParseCloud();
|
addParseCloud();
|
||||||
if (typeof cloud === 'function') {
|
if (typeof cloud === 'function') {
|
||||||
@@ -106,7 +113,6 @@ function ParseServer({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const filesControllerAdapter = loadAdapter(filesAdapter, GridStoreAdapter);
|
const filesControllerAdapter = loadAdapter(filesAdapter, GridStoreAdapter);
|
||||||
const pushControllerAdapter = loadAdapter(push, ParsePushAdapter);
|
const pushControllerAdapter = loadAdapter(push, ParsePushAdapter);
|
||||||
const loggerControllerAdapter = loadAdapter(loggerAdapter, FileLoggerAdapter);
|
const loggerControllerAdapter = loadAdapter(loggerAdapter, FileLoggerAdapter);
|
||||||
@@ -116,6 +122,7 @@ function ParseServer({
|
|||||||
const filesController = new FilesController(filesControllerAdapter);
|
const filesController = new FilesController(filesControllerAdapter);
|
||||||
const pushController = new PushController(pushControllerAdapter);
|
const pushController = new PushController(pushControllerAdapter);
|
||||||
const loggerController = new LoggerController(loggerControllerAdapter);
|
const loggerController = new LoggerController(loggerControllerAdapter);
|
||||||
|
const hooksController = new HooksController(appId);
|
||||||
|
|
||||||
cache.apps[appId] = {
|
cache.apps[appId] = {
|
||||||
masterKey: masterKey,
|
masterKey: masterKey,
|
||||||
@@ -129,6 +136,7 @@ function ParseServer({
|
|||||||
filesController: filesController,
|
filesController: filesController,
|
||||||
pushController: pushController,
|
pushController: pushController,
|
||||||
loggerController: loggerController,
|
loggerController: loggerController,
|
||||||
|
hooksController: hooksController,
|
||||||
enableAnonymousUsers: enableAnonymousUsers,
|
enableAnonymousUsers: enableAnonymousUsers,
|
||||||
oauth: oauth,
|
oauth: oauth,
|
||||||
};
|
};
|
||||||
@@ -138,10 +146,6 @@ function ParseServer({
|
|||||||
cache.apps[appId]['facebookAppIds'].push(process.env.FACEBOOK_APP_ID);
|
cache.apps[appId]['facebookAppIds'].push(process.env.FACEBOOK_APP_ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize the node client SDK automatically
|
|
||||||
Parse.initialize(appId, javascriptKey, masterKey);
|
|
||||||
Parse.serverURL = serverURL;
|
|
||||||
|
|
||||||
// This app serves the Parse API directly.
|
// This app serves the Parse API directly.
|
||||||
// It's the equivalent of https://api.parse.com/1 in the hosted Parse API.
|
// It's the equivalent of https://api.parse.com/1 in the hosted Parse API.
|
||||||
var api = express();
|
var api = express();
|
||||||
@@ -179,6 +183,10 @@ function ParseServer({
|
|||||||
routers.push(require('./global_config'));
|
routers.push(require('./global_config'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (process.env.PARSE_EXPERIMENTAL_HOOKS_ENABLED || process.env.TESTING) {
|
||||||
|
routers.push(new HooksRouter());
|
||||||
|
}
|
||||||
|
|
||||||
let appRouter = new PromiseRouter();
|
let appRouter = new PromiseRouter();
|
||||||
routers.forEach((router) => {
|
routers.forEach((router) => {
|
||||||
appRouter.merge(router);
|
appRouter.merge(router);
|
||||||
@@ -189,7 +197,6 @@ function ParseServer({
|
|||||||
|
|
||||||
api.use(middlewares.handleParseErrors);
|
api.use(middlewares.handleParseErrors);
|
||||||
|
|
||||||
|
|
||||||
process.on('uncaughtException', (err) => {
|
process.on('uncaughtException', (err) => {
|
||||||
if( err.code === "EADDRINUSE" ) { // user-friendly message for this common error
|
if( err.code === "EADDRINUSE" ) { // user-friendly message for this common error
|
||||||
console.log(`Unable to listen on port ${err.port}. The port is already in use.`);
|
console.log(`Unable to listen on port ${err.port}. The port is already in use.`);
|
||||||
@@ -199,52 +206,14 @@ function ParseServer({
|
|||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
hooksController.load();
|
||||||
|
|
||||||
return api;
|
return api;
|
||||||
}
|
}
|
||||||
|
|
||||||
function addParseCloud() {
|
function addParseCloud() {
|
||||||
Parse.Cloud.Functions = {};
|
const ParseCloud = require("./cloud-code/Parse.Cloud");
|
||||||
Parse.Cloud.Validators = {};
|
Object.assign(Parse.Cloud, ParseCloud);
|
||||||
Parse.Cloud.Triggers = {
|
|
||||||
beforeSave: {},
|
|
||||||
beforeDelete: {},
|
|
||||||
afterSave: {},
|
|
||||||
afterDelete: {}
|
|
||||||
};
|
|
||||||
|
|
||||||
function validateClassNameForTriggers(className) {
|
|
||||||
const restrictedClassNames = [ '_Session' ];
|
|
||||||
if (restrictedClassNames.indexOf(className) != -1) {
|
|
||||||
throw `Triggers are not supported for ${className} class.`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Parse.Cloud.define = function(functionName, handler, validationHandler) {
|
|
||||||
Parse.Cloud.Functions[functionName] = handler;
|
|
||||||
Parse.Cloud.Validators[functionName] = validationHandler;
|
|
||||||
};
|
|
||||||
Parse.Cloud.beforeSave = function(parseClass, handler) {
|
|
||||||
let className = getClassName(parseClass);
|
|
||||||
validateClassNameForTriggers(className);
|
|
||||||
Parse.Cloud.Triggers.beforeSave[className] = handler;
|
|
||||||
};
|
|
||||||
Parse.Cloud.beforeDelete = function(parseClass, handler) {
|
|
||||||
let className = getClassName(parseClass);
|
|
||||||
validateClassNameForTriggers(className);
|
|
||||||
Parse.Cloud.Triggers.beforeDelete[className] = handler;
|
|
||||||
};
|
|
||||||
Parse.Cloud.afterSave = function(parseClass, handler) {
|
|
||||||
let className = getClassName(parseClass);
|
|
||||||
validateClassNameForTriggers(className);
|
|
||||||
Parse.Cloud.Triggers.afterSave[className] = handler;
|
|
||||||
};
|
|
||||||
Parse.Cloud.afterDelete = function(parseClass, handler) {
|
|
||||||
let className = getClassName(parseClass);
|
|
||||||
validateClassNameForTriggers(className);
|
|
||||||
Parse.Cloud.Triggers.afterDelete[className] = handler;
|
|
||||||
};
|
|
||||||
Parse.Cloud.httpRequest = httpRequest;
|
|
||||||
global.Parse = Parse;
|
global.Parse = Parse;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
12
src/rest.js
12
src/rest.js
@@ -39,8 +39,8 @@ function del(config, auth, className, objectId) {
|
|||||||
var inflatedObject;
|
var inflatedObject;
|
||||||
|
|
||||||
return Promise.resolve().then(() => {
|
return Promise.resolve().then(() => {
|
||||||
if (triggers.getTrigger(className, 'beforeDelete') ||
|
if (triggers.getTrigger(className, 'beforeDelete', config.applicationId) ||
|
||||||
triggers.getTrigger(className, 'afterDelete') ||
|
triggers.getTrigger(className, 'afterDelete', config.applicationId) ||
|
||||||
className == '_Session') {
|
className == '_Session') {
|
||||||
return find(config, auth, className, {objectId: objectId})
|
return find(config, auth, className, {objectId: objectId})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
@@ -49,7 +49,7 @@ function del(config, auth, className, objectId) {
|
|||||||
cache.clearUser(response.results[0].sessionToken);
|
cache.clearUser(response.results[0].sessionToken);
|
||||||
inflatedObject = Parse.Object.fromJSON(response.results[0]);
|
inflatedObject = Parse.Object.fromJSON(response.results[0]);
|
||||||
return triggers.maybeRunTrigger('beforeDelete',
|
return triggers.maybeRunTrigger('beforeDelete',
|
||||||
auth, inflatedObject);
|
auth, inflatedObject, null, config.applicationId);
|
||||||
}
|
}
|
||||||
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND,
|
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND,
|
||||||
'Object not found for delete.');
|
'Object not found for delete.');
|
||||||
@@ -76,7 +76,7 @@ function del(config, auth, className, objectId) {
|
|||||||
objectId: objectId
|
objectId: objectId
|
||||||
}, options);
|
}, options);
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
triggers.maybeRunTrigger('afterDelete', auth, inflatedObject);
|
triggers.maybeRunTrigger('afterDelete', auth, inflatedObject, null, config.applicationId);
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -96,8 +96,8 @@ function update(config, auth, className, objectId, restObject) {
|
|||||||
enforceRoleSecurity('update', className, auth);
|
enforceRoleSecurity('update', className, auth);
|
||||||
|
|
||||||
return Promise.resolve().then(() => {
|
return Promise.resolve().then(() => {
|
||||||
if (triggers.getTrigger(className, 'beforeSave') ||
|
if (triggers.getTrigger(className, 'beforeSave', config.applicationId) ||
|
||||||
triggers.getTrigger(className, 'afterSave')) {
|
triggers.getTrigger(className, 'afterSave', config.applicationId)) {
|
||||||
return find(config, auth, className, {objectId: objectId});
|
return find(config, auth, className, {objectId: objectId});
|
||||||
}
|
}
|
||||||
return Promise.resolve({});
|
return Promise.resolve({});
|
||||||
|
|||||||
116
src/triggers.js
116
src/triggers.js
@@ -1,6 +1,6 @@
|
|||||||
// triggers.js
|
// triggers.js
|
||||||
|
var Parse = require('parse/node').Parse,
|
||||||
var Parse = require('parse/node').Parse;
|
cache = require('./cache');
|
||||||
|
|
||||||
var Types = {
|
var Types = {
|
||||||
beforeSave: 'beforeSave',
|
beforeSave: 'beforeSave',
|
||||||
@@ -9,11 +9,60 @@ var Types = {
|
|||||||
afterDelete: 'afterDelete'
|
afterDelete: 'afterDelete'
|
||||||
};
|
};
|
||||||
|
|
||||||
var getTrigger = function(className, triggerType) {
|
var BaseStore = function() {
|
||||||
if (Parse.Cloud.Triggers
|
this.Functions = {}
|
||||||
&& Parse.Cloud.Triggers[triggerType]
|
this.Validators = {}
|
||||||
&& Parse.Cloud.Triggers[triggerType][className]) {
|
this.Triggers = Object.keys(Types).reduce(function(base, key){
|
||||||
return Parse.Cloud.Triggers[triggerType][className];
|
base[key] = {};
|
||||||
|
return base;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
var _triggerStore = {};
|
||||||
|
|
||||||
|
function addFunction(functionName, handler, validationHandler, applicationId) {
|
||||||
|
applicationId = applicationId || Parse.applicationId;
|
||||||
|
_triggerStore[applicationId] = _triggerStore[applicationId] || new BaseStore();
|
||||||
|
_triggerStore[applicationId].Functions[functionName] = handler;
|
||||||
|
_triggerStore[applicationId].Validators[functionName] = validationHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
function addTrigger(type, className, handler, applicationId) {
|
||||||
|
applicationId = applicationId || Parse.applicationId;
|
||||||
|
_triggerStore[applicationId] = _triggerStore[applicationId] || new BaseStore();
|
||||||
|
_triggerStore[applicationId].Triggers[type][className] = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeFunction(functionName, applicationId) {
|
||||||
|
applicationId = applicationId || Parse.applicationId;
|
||||||
|
delete _triggerStore[applicationId].Functions[functionName]
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeTrigger(type, className, applicationId) {
|
||||||
|
applicationId = applicationId || Parse.applicationId;
|
||||||
|
delete _triggerStore[applicationId].Triggers[type][className]
|
||||||
|
}
|
||||||
|
|
||||||
|
function _unregister(a,b,c,d) {
|
||||||
|
if (d) {
|
||||||
|
removeTrigger(c,d,a);
|
||||||
|
delete _triggerStore[a][b][c][d];
|
||||||
|
} else {
|
||||||
|
delete _triggerStore[a][b][c];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var getTrigger = function(className, triggerType, applicationId) {
|
||||||
|
if (!applicationId) {
|
||||||
|
throw "Missing ApplicationID";
|
||||||
|
}
|
||||||
|
var manager = _triggerStore[applicationId]
|
||||||
|
if (manager
|
||||||
|
&& manager.Triggers
|
||||||
|
&& manager.Triggers[triggerType]
|
||||||
|
&& manager.Triggers[triggerType][className]) {
|
||||||
|
return manager.Triggers[triggerType][className];
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
};
|
};
|
||||||
@@ -22,6 +71,22 @@ function triggerExists(className: string, type: string): boolean {
|
|||||||
return (getTrigger(className, type) != undefined);
|
return (getTrigger(className, type) != undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var getFunction = function(functionName, applicationId) {
|
||||||
|
var manager = _triggerStore[applicationId];
|
||||||
|
if (manager && manager.Functions) {
|
||||||
|
return manager.Functions[functionName];
|
||||||
|
};
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
var getValidator = function(functionName, applicationId) {
|
||||||
|
var manager = _triggerStore[applicationId];
|
||||||
|
if (manager && manager.Validators) {
|
||||||
|
return manager.Validators[functionName];
|
||||||
|
};
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
var getRequestObject = function(triggerType, auth, parseObject, originalParseObject) {
|
var getRequestObject = function(triggerType, auth, parseObject, originalParseObject) {
|
||||||
var request = {
|
var request = {
|
||||||
triggerName: triggerType,
|
triggerName: triggerType,
|
||||||
@@ -53,8 +118,12 @@ var getRequestObject = function(triggerType, auth, parseObject, originalParseObj
|
|||||||
// Any changes made to the object in a beforeSave will be included.
|
// Any changes made to the object in a beforeSave will be included.
|
||||||
var getResponseObject = function(request, resolve, reject) {
|
var getResponseObject = function(request, resolve, reject) {
|
||||||
return {
|
return {
|
||||||
success: function() {
|
success: function(response) {
|
||||||
var response = {};
|
// Use the JSON response
|
||||||
|
if (response && request.triggerName === Types.beforeSave) {
|
||||||
|
return resolve(response);
|
||||||
|
}
|
||||||
|
response = {};
|
||||||
if (request.triggerName === Types.beforeSave) {
|
if (request.triggerName === Types.beforeSave) {
|
||||||
response['object'] = request.object._getSaveJSON();
|
response['object'] = request.object._getSaveJSON();
|
||||||
}
|
}
|
||||||
@@ -72,15 +141,19 @@ var getResponseObject = function(request, resolve, reject) {
|
|||||||
// Resolves to an object, empty or containing an object key. A beforeSave
|
// Resolves to an object, empty or containing an object key. A beforeSave
|
||||||
// trigger will set the object key to the rest format object to save.
|
// trigger will set the object key to the rest format object to save.
|
||||||
// originalParseObject is optional, we only need that for befote/afterSave functions
|
// originalParseObject is optional, we only need that for befote/afterSave functions
|
||||||
var maybeRunTrigger = function(triggerType, auth, parseObject, originalParseObject) {
|
var maybeRunTrigger = function(triggerType, auth, parseObject, originalParseObject, applicationId) {
|
||||||
if (!parseObject) {
|
if (!parseObject) {
|
||||||
return Promise.resolve({});
|
return Promise.resolve({});
|
||||||
}
|
}
|
||||||
return new Promise(function (resolve, reject) {
|
return new Promise(function (resolve, reject) {
|
||||||
var trigger = getTrigger(parseObject.className, triggerType);
|
var trigger = getTrigger(parseObject.className, triggerType, applicationId);
|
||||||
if (!trigger) return resolve({});
|
if (!trigger) return resolve();
|
||||||
var request = getRequestObject(triggerType, auth, parseObject, originalParseObject);
|
var request = getRequestObject(triggerType, auth, parseObject, originalParseObject);
|
||||||
var response = getResponseObject(request, resolve, reject);
|
var response = getResponseObject(request, resolve, reject);
|
||||||
|
// Force the current Parse app before the trigger
|
||||||
|
Parse.applicationId = applicationId;
|
||||||
|
Parse.javascriptKey = cache.apps[applicationId].javascriptKey || '';
|
||||||
|
Parse.masterKey = cache.apps[applicationId].masterKey;
|
||||||
trigger(request, response);
|
trigger(request, response);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -95,6 +168,7 @@ function inflate(data, restObject) {
|
|||||||
return Parse.Object.fromJSON(copy);
|
return Parse.Object.fromJSON(copy);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<<<<<<< 5fae41183ed476976ff29a4c247aa78b00b83a9e
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getTrigger: getTrigger,
|
getTrigger: getTrigger,
|
||||||
getRequestObject: getRequestObject,
|
getRequestObject: getRequestObject,
|
||||||
@@ -103,3 +177,21 @@ module.exports = {
|
|||||||
triggerExists: triggerExists,
|
triggerExists: triggerExists,
|
||||||
Types: Types
|
Types: Types
|
||||||
};
|
};
|
||||||
|
=======
|
||||||
|
var TriggerManager = {};
|
||||||
|
|
||||||
|
TriggerManager.getTrigger = getTrigger;
|
||||||
|
TriggerManager.getRequestObject = getRequestObject;
|
||||||
|
TriggerManager.inflate = inflate;
|
||||||
|
TriggerManager.maybeRunTrigger = maybeRunTrigger;
|
||||||
|
TriggerManager.Types = Types;
|
||||||
|
TriggerManager.addFunction = addFunction;
|
||||||
|
TriggerManager.getFunction = getFunction;
|
||||||
|
TriggerManager.removeTrigger = removeTrigger;
|
||||||
|
TriggerManager.removeFunction = removeFunction;
|
||||||
|
TriggerManager.getValidator = getValidator;
|
||||||
|
TriggerManager.addTrigger = addTrigger;
|
||||||
|
TriggerManager._unregister = _unregister;
|
||||||
|
|
||||||
|
module.exports = TriggerManager;
|
||||||
|
>>>>>>> Adds Hooks API
|
||||||
|
|||||||
Reference in New Issue
Block a user