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
|
||||
.idea
|
||||
|
||||
# visual studio code
|
||||
.vscode
|
||||
|
||||
# Babel.js
|
||||
lib/
|
||||
|
||||
# cache folder
|
||||
.cache
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
var httpRequest = require("../src/httpRequest"),
|
||||
var httpRequest = require("../src/cloud-code/httpRequest"),
|
||||
bodyParser = require('body-parser'),
|
||||
express = require("express");
|
||||
|
||||
|
||||
@@ -786,7 +786,11 @@ describe('Parse.ACL', () => {
|
||||
equal(results.length, 1);
|
||||
var result = results[0];
|
||||
ok(result);
|
||||
equal(result.id, object.id);
|
||||
if (!result) {
|
||||
fail("should have result");
|
||||
} else {
|
||||
equal(result.id, object.id);
|
||||
}
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -275,7 +275,11 @@ describe('miscellaneous', function() {
|
||||
var objAgain = new Parse.Object('BeforeDeleteFail', {objectId: id});
|
||||
return objAgain.fetch();
|
||||
}).then((objAgain) => {
|
||||
expect(objAgain.get('foo')).toEqual('bar');
|
||||
if (objAgain) {
|
||||
expect(objAgain.get('foo')).toEqual('bar');
|
||||
} else {
|
||||
fail("unable to fetch the object ", id);
|
||||
}
|
||||
done();
|
||||
}, (error) => {
|
||||
// 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) {
|
||||
Parse.Cloud.run('foo').then((result) => {
|
||||
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.get('x')).toEqual(2);
|
||||
var bar = result.object.get('relation');
|
||||
@@ -381,23 +390,25 @@ describe('miscellaneous', function() {
|
||||
expect(results.length).toEqual(1);
|
||||
expect(results[0]['foo']).toEqual('bar');
|
||||
done();
|
||||
});
|
||||
}).fail( err => {
|
||||
fail(err);
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
describe('beforeSave', () => {
|
||||
beforeEach(done => {
|
||||
// Make sure the required mock for all tests is unset.
|
||||
delete Parse.Cloud.Triggers.beforeSave.GameScore;
|
||||
Parse.Cloud._removeHook("Triggers", "beforeSave", "GameScore");
|
||||
done();
|
||||
});
|
||||
|
||||
afterEach(done => {
|
||||
// Make sure the required mock for all tests is unset.
|
||||
delete Parse.Cloud.Triggers.beforeSave.GameScore;
|
||||
Parse.Cloud._removeHook("Triggers", "beforeSave", "GameScore");
|
||||
done();
|
||||
});
|
||||
|
||||
it('object is set on create and update', done => {
|
||||
});
|
||||
|
||||
it('object is set on create and update', done => {
|
||||
let triggerTime = 0;
|
||||
// Register a mock beforeSave hook
|
||||
Parse.Cloud.beforeSave('GameScore', (req, res) => {
|
||||
@@ -610,8 +621,8 @@ describe('miscellaneous', function() {
|
||||
}).then(function() {
|
||||
// Make sure the checking has been triggered
|
||||
expect(triggerTime).toBe(2);
|
||||
// Clear mock afterSave
|
||||
delete Parse.Cloud.Triggers.afterSave.GameScore;
|
||||
// Clear mock beforeSave
|
||||
Parse.Cloud._removeHook("Triggers", "beforeSave", "GameScore");
|
||||
done();
|
||||
}, function(error) {
|
||||
fail(error);
|
||||
@@ -663,9 +674,10 @@ describe('miscellaneous', function() {
|
||||
// Make sure the checking has been triggered
|
||||
expect(triggerTime).toBe(2);
|
||||
// Clear mock afterSave
|
||||
delete Parse.Cloud.Triggers.afterSave.GameScore;
|
||||
Parse.Cloud._removeHook("Triggers", "afterSave", "GameScore");
|
||||
done();
|
||||
}, function(error) {
|
||||
console.error(error);
|
||||
fail(error);
|
||||
done();
|
||||
});
|
||||
@@ -678,12 +690,12 @@ describe('miscellaneous', function() {
|
||||
});
|
||||
Parse.Cloud.run('willFail').then((s) => {
|
||||
fail('Should not have succeeded.');
|
||||
delete Parse.Cloud.Functions['willFail'];
|
||||
Parse.Cloud._removeHook("Functions", "willFail");
|
||||
done();
|
||||
}, (e) => {
|
||||
expect(e.code).toEqual(141);
|
||||
expect(e.message).toEqual('noway');
|
||||
delete Parse.Cloud.Functions['willFail'];
|
||||
Parse.Cloud._removeHook("Functions", "willFail");
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -712,7 +724,7 @@ describe('miscellaneous', function() {
|
||||
// Make sure query string params override body params
|
||||
expect(res.other).toEqual('2');
|
||||
expect(res.foo).toEqual("bar");
|
||||
delete Parse.Cloud.Functions['echoParams'];
|
||||
Parse.Cloud._removeHook("Functions",'echoParams');
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -726,7 +738,7 @@ describe('miscellaneous', function() {
|
||||
});
|
||||
|
||||
Parse.Cloud.run('functionWithParameterValidation', {"success":100}).then((s) => {
|
||||
delete Parse.Cloud.Functions['functionWithParameterValidation'];
|
||||
Parse.Cloud._removeHook("Functions", "functionWithParameterValidation");
|
||||
done();
|
||||
}, (e) => {
|
||||
fail('Validation should not have failed.');
|
||||
@@ -744,7 +756,7 @@ describe('miscellaneous', function() {
|
||||
|
||||
Parse.Cloud.run('functionWithParameterValidationFailure', {"success":500}).then((s) => {
|
||||
fail('Validation should not have succeeded');
|
||||
delete Parse.Cloud.Functions['functionWithParameterValidationFailure'];
|
||||
Parse.Cloud._removeHook("Functions", "functionWithParameterValidationFailure");
|
||||
done();
|
||||
}, (e) => {
|
||||
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) {
|
||||
res.success('Hello world!');
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// A Config object provides information about how a specific app is
|
||||
// configured.
|
||||
// mount is the URL for the root of the API; includes http, domain, etc.
|
||||
|
||||
export class Config {
|
||||
|
||||
constructor(applicationId, mount) {
|
||||
@@ -23,8 +24,8 @@ export class Config {
|
||||
this.fileKey = cacheInfo.fileKey;
|
||||
this.facebookAppIds = cacheInfo.facebookAppIds;
|
||||
this.enableAnonymousUsers = cacheInfo.enableAnonymousUsers;
|
||||
|
||||
this.database = DatabaseAdapter.getDatabaseConnection(applicationId);
|
||||
this.hooksController = cacheInfo.hooksController;
|
||||
this.filesController = cacheInfo.filesController;
|
||||
this.pushController = cacheInfo.pushController;
|
||||
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) {
|
||||
case 'POST':
|
||||
case 'GET':
|
||||
@@ -42,6 +42,25 @@ export default class PromiseRouter {
|
||||
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({
|
||||
path: path,
|
||||
method: method,
|
||||
@@ -58,7 +77,6 @@ export default class PromiseRouter {
|
||||
if (route.method != method) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// NOTE: we can only route the specific wildcards :className and
|
||||
// :objectId, and in that order.
|
||||
// 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 triggers.maybeRunTrigger(
|
||||
'beforeSave', this.auth, updatedObject, originalObject);
|
||||
'beforeSave', this.auth, updatedObject, originalObject, this.config.applicationId);
|
||||
}).then((response) => {
|
||||
if (response && response.object) {
|
||||
this.data = response.object;
|
||||
@@ -294,7 +294,7 @@ RestWrite.prototype.handleOAuthAuthData = function(provider) {
|
||||
if (!validateAuthData || !validateAppId) {
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
return validateAuthData(authData, oauthOptions)
|
||||
.then(() => {
|
||||
if (appIds && typeof validateAppId === "function") {
|
||||
@@ -789,7 +789,7 @@ RestWrite.prototype.runAfterTrigger = function() {
|
||||
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.
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import PromiseRouter from '../PromiseRouter';
|
||||
import express from 'express';
|
||||
import BodyParser from 'body-parser';
|
||||
import * as Middlewares from '../middlewares';
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
// functions.js
|
||||
// FunctionsRouter.js
|
||||
|
||||
var express = require('express'),
|
||||
Parse = require('parse/node').Parse;
|
||||
Parse = require('parse/node').Parse,
|
||||
triggers = require('../triggers');
|
||||
|
||||
import PromiseRouter from '../PromiseRouter';
|
||||
|
||||
@@ -27,17 +28,21 @@ export class FunctionsRouter extends PromiseRouter {
|
||||
}
|
||||
|
||||
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 = {
|
||||
params: Object.assign({}, req.body, req.query),
|
||||
params: params,
|
||||
master: req.auth && req.auth.isMaster,
|
||||
user: req.auth && req.auth.user,
|
||||
installationId: req.info.installationId
|
||||
};
|
||||
|
||||
if (Parse.Cloud.Validators[req.params.functionName]) {
|
||||
var result = Parse.Cloud.Validators[req.params.functionName](request);
|
||||
if (theValidator && typeof theValidator === "function") {
|
||||
var result = theValidator(request);
|
||||
if (!result) {
|
||||
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) {
|
||||
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 {
|
||||
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
|
||||
73
src/index.js
73
src/index.js
@@ -9,8 +9,7 @@ var batch = require('./batch'),
|
||||
express = require('express'),
|
||||
middlewares = require('./middlewares'),
|
||||
multer = require('multer'),
|
||||
Parse = require('parse/node').Parse,
|
||||
httpRequest = require('./httpRequest');
|
||||
Parse = require('parse/node').Parse;
|
||||
|
||||
import PromiseRouter from './PromiseRouter';
|
||||
import { GridStoreAdapter } from './Adapters/Files/GridStoreAdapter';
|
||||
@@ -32,10 +31,12 @@ import { IAPValidationRouter } from './Routers/IAPValidationRouter';
|
||||
import { PushRouter } from './Routers/PushRouter';
|
||||
import { FilesRouter } from './Routers/FilesRouter';
|
||||
import { LogsRouter } from './Routers/LogsRouter';
|
||||
import { HooksRouter } from './Routers/HooksRouter';
|
||||
|
||||
import { loadAdapter } from './Adapters/AdapterLoader';
|
||||
import { FileLoggerAdapter } from './Adapters/Logger/FileLoggerAdapter';
|
||||
import { LoggerController } from './Controllers/LoggerController';
|
||||
import { HooksController } from './Controllers/HooksController';
|
||||
|
||||
import requiredParameter from './requiredParameter';
|
||||
// Mutate the Parse object to add the Cloud Code handlers
|
||||
@@ -88,6 +89,11 @@ function ParseServer({
|
||||
serverURL = requiredParameter('You must provide a serverURL!'),
|
||||
maxUploadSize = '20mb'
|
||||
}) {
|
||||
|
||||
// Initialize the node client SDK automatically
|
||||
Parse.initialize(appId, javascriptKey || '', masterKey);
|
||||
Parse.serverURL = serverURL || '';
|
||||
|
||||
if (databaseAdapter) {
|
||||
DatabaseAdapter.setAdapter(databaseAdapter);
|
||||
}
|
||||
@@ -95,6 +101,7 @@ function ParseServer({
|
||||
if (databaseURI) {
|
||||
DatabaseAdapter.setAppDatabaseURI(appId, databaseURI);
|
||||
}
|
||||
|
||||
if (cloud) {
|
||||
addParseCloud();
|
||||
if (typeof cloud === 'function') {
|
||||
@@ -106,7 +113,6 @@ function ParseServer({
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const filesControllerAdapter = loadAdapter(filesAdapter, GridStoreAdapter);
|
||||
const pushControllerAdapter = loadAdapter(push, ParsePushAdapter);
|
||||
const loggerControllerAdapter = loadAdapter(loggerAdapter, FileLoggerAdapter);
|
||||
@@ -116,7 +122,8 @@ function ParseServer({
|
||||
const filesController = new FilesController(filesControllerAdapter);
|
||||
const pushController = new PushController(pushControllerAdapter);
|
||||
const loggerController = new LoggerController(loggerControllerAdapter);
|
||||
|
||||
const hooksController = new HooksController(appId);
|
||||
|
||||
cache.apps[appId] = {
|
||||
masterKey: masterKey,
|
||||
collectionPrefix: collectionPrefix,
|
||||
@@ -129,19 +136,16 @@ function ParseServer({
|
||||
filesController: filesController,
|
||||
pushController: pushController,
|
||||
loggerController: loggerController,
|
||||
hooksController: hooksController,
|
||||
enableAnonymousUsers: enableAnonymousUsers,
|
||||
oauth: oauth,
|
||||
};
|
||||
};
|
||||
|
||||
// To maintain compatibility. TODO: Remove in v2.1
|
||||
if (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.
|
||||
// It's the equivalent of https://api.parse.com/1 in the hosted Parse API.
|
||||
var api = express();
|
||||
@@ -178,6 +182,10 @@ function ParseServer({
|
||||
if (process.env.PARSE_EXPERIMENTAL_CONFIG_ENABLED || process.env.TESTING) {
|
||||
routers.push(require('./global_config'));
|
||||
}
|
||||
|
||||
if (process.env.PARSE_EXPERIMENTAL_HOOKS_ENABLED || process.env.TESTING) {
|
||||
routers.push(new HooksRouter());
|
||||
}
|
||||
|
||||
let appRouter = new PromiseRouter();
|
||||
routers.forEach((router) => {
|
||||
@@ -189,7 +197,6 @@ function ParseServer({
|
||||
|
||||
api.use(middlewares.handleParseErrors);
|
||||
|
||||
|
||||
process.on('uncaughtException', (err) => {
|
||||
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.`);
|
||||
@@ -199,52 +206,14 @@ function ParseServer({
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
hooksController.load();
|
||||
|
||||
return api;
|
||||
}
|
||||
|
||||
function addParseCloud() {
|
||||
Parse.Cloud.Functions = {};
|
||||
Parse.Cloud.Validators = {};
|
||||
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;
|
||||
const ParseCloud = require("./cloud-code/Parse.Cloud");
|
||||
Object.assign(Parse.Cloud, ParseCloud);
|
||||
global.Parse = Parse;
|
||||
}
|
||||
|
||||
@@ -258,4 +227,4 @@ function getClassName(parseClass) {
|
||||
module.exports = {
|
||||
ParseServer: ParseServer,
|
||||
S3Adapter: S3Adapter
|
||||
};
|
||||
};
|
||||
12
src/rest.js
12
src/rest.js
@@ -39,8 +39,8 @@ function del(config, auth, className, objectId) {
|
||||
var inflatedObject;
|
||||
|
||||
return Promise.resolve().then(() => {
|
||||
if (triggers.getTrigger(className, 'beforeDelete') ||
|
||||
triggers.getTrigger(className, 'afterDelete') ||
|
||||
if (triggers.getTrigger(className, 'beforeDelete', config.applicationId) ||
|
||||
triggers.getTrigger(className, 'afterDelete', config.applicationId) ||
|
||||
className == '_Session') {
|
||||
return find(config, auth, className, {objectId: objectId})
|
||||
.then((response) => {
|
||||
@@ -49,7 +49,7 @@ function del(config, auth, className, objectId) {
|
||||
cache.clearUser(response.results[0].sessionToken);
|
||||
inflatedObject = Parse.Object.fromJSON(response.results[0]);
|
||||
return triggers.maybeRunTrigger('beforeDelete',
|
||||
auth, inflatedObject);
|
||||
auth, inflatedObject, null, config.applicationId);
|
||||
}
|
||||
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND,
|
||||
'Object not found for delete.');
|
||||
@@ -76,7 +76,7 @@ function del(config, auth, className, objectId) {
|
||||
objectId: objectId
|
||||
}, options);
|
||||
}).then(() => {
|
||||
triggers.maybeRunTrigger('afterDelete', auth, inflatedObject);
|
||||
triggers.maybeRunTrigger('afterDelete', auth, inflatedObject, null, config.applicationId);
|
||||
return Promise.resolve();
|
||||
});
|
||||
}
|
||||
@@ -96,8 +96,8 @@ function update(config, auth, className, objectId, restObject) {
|
||||
enforceRoleSecurity('update', className, auth);
|
||||
|
||||
return Promise.resolve().then(() => {
|
||||
if (triggers.getTrigger(className, 'beforeSave') ||
|
||||
triggers.getTrigger(className, 'afterSave')) {
|
||||
if (triggers.getTrigger(className, 'beforeSave', config.applicationId) ||
|
||||
triggers.getTrigger(className, 'afterSave', config.applicationId)) {
|
||||
return find(config, auth, className, {objectId: objectId});
|
||||
}
|
||||
return Promise.resolve({});
|
||||
|
||||
116
src/triggers.js
116
src/triggers.js
@@ -1,6 +1,6 @@
|
||||
// triggers.js
|
||||
|
||||
var Parse = require('parse/node').Parse;
|
||||
var Parse = require('parse/node').Parse,
|
||||
cache = require('./cache');
|
||||
|
||||
var Types = {
|
||||
beforeSave: 'beforeSave',
|
||||
@@ -9,11 +9,60 @@ var Types = {
|
||||
afterDelete: 'afterDelete'
|
||||
};
|
||||
|
||||
var getTrigger = function(className, triggerType) {
|
||||
if (Parse.Cloud.Triggers
|
||||
&& Parse.Cloud.Triggers[triggerType]
|
||||
&& Parse.Cloud.Triggers[triggerType][className]) {
|
||||
return Parse.Cloud.Triggers[triggerType][className];
|
||||
var BaseStore = function() {
|
||||
this.Functions = {}
|
||||
this.Validators = {}
|
||||
this.Triggers = Object.keys(Types).reduce(function(base, key){
|
||||
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;
|
||||
};
|
||||
@@ -22,6 +71,22 @@ function triggerExists(className: string, type: string): boolean {
|
||||
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 request = {
|
||||
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.
|
||||
var getResponseObject = function(request, resolve, reject) {
|
||||
return {
|
||||
success: function() {
|
||||
var response = {};
|
||||
success: function(response) {
|
||||
// Use the JSON response
|
||||
if (response && request.triggerName === Types.beforeSave) {
|
||||
return resolve(response);
|
||||
}
|
||||
response = {};
|
||||
if (request.triggerName === Types.beforeSave) {
|
||||
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
|
||||
// trigger will set the object key to the rest format object to save.
|
||||
// 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) {
|
||||
return Promise.resolve({});
|
||||
}
|
||||
return new Promise(function (resolve, reject) {
|
||||
var trigger = getTrigger(parseObject.className, triggerType);
|
||||
if (!trigger) return resolve({});
|
||||
var trigger = getTrigger(parseObject.className, triggerType, applicationId);
|
||||
if (!trigger) return resolve();
|
||||
var request = getRequestObject(triggerType, auth, parseObject, originalParseObject);
|
||||
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);
|
||||
});
|
||||
};
|
||||
@@ -95,6 +168,7 @@ function inflate(data, restObject) {
|
||||
return Parse.Object.fromJSON(copy);
|
||||
}
|
||||
|
||||
<<<<<<< 5fae41183ed476976ff29a4c247aa78b00b83a9e
|
||||
module.exports = {
|
||||
getTrigger: getTrigger,
|
||||
getRequestObject: getRequestObject,
|
||||
@@ -103,3 +177,21 @@ module.exports = {
|
||||
triggerExists: triggerExists,
|
||||
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