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
|
||||||
|
|||||||
14
README.md
14
README.md
@@ -67,6 +67,20 @@ The standalone Parse Server can be configured using [environment variables](#con
|
|||||||
|
|
||||||
Please refer to the [configuration section](#configuration) or help;
|
Please refer to the [configuration section](#configuration) or help;
|
||||||
|
|
||||||
|
To get more help for running the parse-server standalone, you can run:
|
||||||
|
|
||||||
|
`$ npm start -- --help`
|
||||||
|
|
||||||
|
The standalone API server supports loading a configuration file in JSON format:
|
||||||
|
|
||||||
|
`$ npm start -- path/to/your/config.json`
|
||||||
|
|
||||||
|
The default port is 1337, to use a different port set the `--port` option:
|
||||||
|
|
||||||
|
`$ npm start -- --port=8080 path/to/your/config.json`
|
||||||
|
|
||||||
|
Please refer to the [configuration section](#configuration) or help;
|
||||||
|
|
||||||
You can also install Parse Server globally:
|
You can also install Parse Server globally:
|
||||||
|
|
||||||
`$ npm install -g parse-server`
|
`$ npm install -g parse-server`
|
||||||
|
|||||||
@@ -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);
|
||||||
equal(result.id, object.id);
|
if (!result) {
|
||||||
|
fail("should have result");
|
||||||
|
} else {
|
||||||
|
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) => {
|
||||||
expect(objAgain.get('foo')).toEqual('bar');
|
if (objAgain) {
|
||||||
|
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,23 +390,25 @@ 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();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('object is set on create and update', done => {
|
it('object is set on create and update', done => {
|
||||||
let triggerTime = 0;
|
let triggerTime = 0;
|
||||||
// Register a mock beforeSave hook
|
// Register a mock beforeSave hook
|
||||||
Parse.Cloud.beforeSave('GameScore', (req, res) => {
|
Parse.Cloud.beforeSave('GameScore', (req, res) => {
|
||||||
@@ -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!');
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 2000;
|
jasmine.DEFAULT_TIMEOUT_INTERVAL = 2000;
|
||||||
|
|
||||||
var cache = require('../src/cache');
|
var cache = require('../src/cache').default;
|
||||||
var DatabaseAdapter = require('../src/DatabaseAdapter');
|
var DatabaseAdapter = require('../src/DatabaseAdapter');
|
||||||
var express = require('express');
|
var express = require('express');
|
||||||
var facebook = require('../src/oauth/facebook');
|
var facebook = require('../src/oauth/facebook');
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ var deepcopy = require('deepcopy');
|
|||||||
var Parse = require('parse/node').Parse;
|
var Parse = require('parse/node').Parse;
|
||||||
var RestQuery = require('./RestQuery');
|
var RestQuery = require('./RestQuery');
|
||||||
|
|
||||||
var cache = require('./cache');
|
import cache from './cache';
|
||||||
|
|
||||||
// An Auth object tells you who is requesting something and whether
|
// An Auth object tells you who is requesting something and whether
|
||||||
// the master key was used.
|
// the master key was used.
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
|
import cache from './cache';
|
||||||
|
|
||||||
export class Config {
|
export class Config {
|
||||||
|
|
||||||
constructor(applicationId, mount) {
|
constructor(applicationId, mount) {
|
||||||
var cache = require('./cache');
|
|
||||||
var DatabaseAdapter = require('./DatabaseAdapter');
|
var DatabaseAdapter = require('./DatabaseAdapter');
|
||||||
|
|
||||||
var cacheInfo = cache.apps[applicationId];
|
var cacheInfo = cache.apps[applicationId];
|
||||||
@@ -23,8 +26,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;
|
||||||
|
|||||||
230
src/Controllers/HooksController.js
Normal file
230
src/Controllers/HooksController.js
Normal file
@@ -0,0 +1,230 @@
|
|||||||
|
var DatabaseAdapter = require('../DatabaseAdapter'),
|
||||||
|
triggers = require('../triggers'),
|
||||||
|
request = require('request');
|
||||||
|
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(req, res) {
|
||||||
|
var jsonBody = {};
|
||||||
|
for(var i in req) {
|
||||||
|
jsonBody[i] = req[i];
|
||||||
|
}
|
||||||
|
if (req.object) {
|
||||||
|
jsonBody.object = req.object.toJSON();
|
||||||
|
jsonBody.object.className = req.object.className;
|
||||||
|
}
|
||||||
|
if (req.original) {
|
||||||
|
jsonBody.original = req.original.toJSON();
|
||||||
|
jsonBody.original.className = req.original.className;
|
||||||
|
}
|
||||||
|
var jsonRequest = {};
|
||||||
|
jsonRequest.headers = {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
jsonRequest.body = JSON.stringify(jsonBody);
|
||||||
|
|
||||||
|
request.post(hook.url, jsonRequest, function(err, httpResponse, body){
|
||||||
|
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 res.error(err);
|
||||||
|
} else {
|
||||||
|
return res.success(result);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default HooksController;
|
||||||
@@ -13,11 +13,11 @@
|
|||||||
// * This list is incomplete and the database process is not fully modularized.
|
// * This list is incomplete and the database process is not fully modularized.
|
||||||
//
|
//
|
||||||
// Default is ExportAdapter, which uses mongo.
|
// Default is ExportAdapter, which uses mongo.
|
||||||
|
import cache from './cache';
|
||||||
|
|
||||||
var ExportAdapter = require('./ExportAdapter');
|
var ExportAdapter = require('./ExportAdapter');
|
||||||
|
|
||||||
var adapter = ExportAdapter;
|
var adapter = ExportAdapter;
|
||||||
var cache = require('./cache');
|
|
||||||
var dbConnections = {};
|
var dbConnections = {};
|
||||||
var databaseURI = 'mongodb://localhost:27017/parse';
|
var databaseURI = 'mongodb://localhost:27017/parse';
|
||||||
var appDatabaseURIs = {};
|
var appDatabaseURIs = {};
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -2,10 +2,10 @@
|
|||||||
// that writes to the database.
|
// that writes to the database.
|
||||||
// This could be either a "create" or an "update".
|
// This could be either a "create" or an "update".
|
||||||
|
|
||||||
|
import cache from './cache';
|
||||||
var deepcopy = require('deepcopy');
|
var deepcopy = require('deepcopy');
|
||||||
|
|
||||||
var Auth = require('./Auth');
|
var Auth = require('./Auth');
|
||||||
var cache = require('./cache');
|
|
||||||
var Config = require('./Config');
|
var Config = require('./Config');
|
||||||
var cryptoUtils = require('./cryptoUtils');
|
var cryptoUtils = require('./cryptoUtils');
|
||||||
var passwordCrypto = require('./password');
|
var passwordCrypto = require('./password');
|
||||||
@@ -114,7 +114,7 @@ RestWrite.prototype.validateSchema = function() {
|
|||||||
// Any change leads to our data being mutated.
|
// Any change leads to our data being mutated.
|
||||||
RestWrite.prototype.runBeforeTrigger = function() {
|
RestWrite.prototype.runBeforeTrigger = function() {
|
||||||
// Avoid doing any setup for triggers if there is no 'beforeSave' trigger for this class.
|
// Avoid doing any setup for triggers if there is no 'beforeSave' trigger for this class.
|
||||||
if (!triggers.triggerExists(this.className, triggers.Types.beforeSave)) {
|
if (!triggers.triggerExists(this.className, triggers.Types.beforeSave, this.config.applicationId)) {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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;
|
||||||
@@ -294,7 +294,7 @@ RestWrite.prototype.handleOAuthAuthData = function(provider) {
|
|||||||
if (!validateAuthData || !validateAppId) {
|
if (!validateAuthData || !validateAppId) {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
return validateAuthData(authData, oauthOptions)
|
return validateAuthData(authData, oauthOptions)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
if (appIds && typeof validateAppId === "function") {
|
if (appIds && typeof validateAppId === "function") {
|
||||||
@@ -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;
|
||||||
40
src/cache.js
40
src/cache.js
@@ -1,45 +1,45 @@
|
|||||||
var apps = {};
|
export var apps = {};
|
||||||
var stats = {};
|
export var stats = {};
|
||||||
var isLoaded = false;
|
export var isLoaded = false;
|
||||||
var users = {};
|
export var users = {};
|
||||||
|
|
||||||
function getApp(app, callback) {
|
export function getApp(app, callback) {
|
||||||
if (apps[app]) return callback(true, apps[app]);
|
if (apps[app]) return callback(true, apps[app]);
|
||||||
return callback(false);
|
return callback(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateStat(key, value) {
|
export function updateStat(key, value) {
|
||||||
stats[key] = value;
|
stats[key] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getUser(sessionToken) {
|
export function getUser(sessionToken) {
|
||||||
if (users[sessionToken]) return users[sessionToken];
|
if (users[sessionToken]) return users[sessionToken];
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setUser(sessionToken, userObject) {
|
export function setUser(sessionToken, userObject) {
|
||||||
users[sessionToken] = userObject;
|
users[sessionToken] = userObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearUser(sessionToken) {
|
export function clearUser(sessionToken) {
|
||||||
delete users[sessionToken];
|
delete users[sessionToken];
|
||||||
}
|
}
|
||||||
|
|
||||||
//So far used only in tests
|
//So far used only in tests
|
||||||
function clearCache() {
|
export function clearCache() {
|
||||||
apps = {};
|
apps = {};
|
||||||
stats = {};
|
stats = {};
|
||||||
users = {};
|
users = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
export default {
|
||||||
apps: apps,
|
apps,
|
||||||
stats: stats,
|
stats,
|
||||||
isLoaded: isLoaded,
|
isLoaded,
|
||||||
getApp: getApp,
|
getApp,
|
||||||
updateStat: updateStat,
|
updateStat,
|
||||||
clearUser: clearUser,
|
clearUser,
|
||||||
getUser: getUser,
|
getUser,
|
||||||
setUser: setUser,
|
setUser,
|
||||||
clearCache: clearCache,
|
clearCache,
|
||||||
};
|
};
|
||||||
|
|||||||
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
|
||||||
75
src/index.js
75
src/index.js
@@ -4,14 +4,13 @@ import 'babel-polyfill';
|
|||||||
|
|
||||||
var batch = require('./batch'),
|
var batch = require('./batch'),
|
||||||
bodyParser = require('body-parser'),
|
bodyParser = require('body-parser'),
|
||||||
cache = require('./cache'),
|
|
||||||
DatabaseAdapter = require('./DatabaseAdapter'),
|
DatabaseAdapter = require('./DatabaseAdapter'),
|
||||||
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 cache from './cache';
|
||||||
import PromiseRouter from './PromiseRouter';
|
import PromiseRouter from './PromiseRouter';
|
||||||
import { GridStoreAdapter } from './Adapters/Files/GridStoreAdapter';
|
import { GridStoreAdapter } from './Adapters/Files/GridStoreAdapter';
|
||||||
import { S3Adapter } from './Adapters/Files/S3Adapter';
|
import { S3Adapter } from './Adapters/Files/S3Adapter';
|
||||||
@@ -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,7 +122,8 @@ 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,
|
||||||
collectionPrefix: collectionPrefix,
|
collectionPrefix: collectionPrefix,
|
||||||
@@ -129,19 +136,16 @@ function ParseServer({
|
|||||||
filesController: filesController,
|
filesController: filesController,
|
||||||
pushController: pushController,
|
pushController: pushController,
|
||||||
loggerController: loggerController,
|
loggerController: loggerController,
|
||||||
|
hooksController: hooksController,
|
||||||
enableAnonymousUsers: enableAnonymousUsers,
|
enableAnonymousUsers: enableAnonymousUsers,
|
||||||
oauth: oauth,
|
oauth: oauth,
|
||||||
};
|
};
|
||||||
|
|
||||||
// To maintain compatibility. TODO: Remove in v2.1
|
// To maintain compatibility. TODO: Remove in v2.1
|
||||||
if (process.env.FACEBOOK_APP_ID) {
|
if (process.env.FACEBOOK_APP_ID) {
|
||||||
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();
|
||||||
@@ -178,6 +182,10 @@ function ParseServer({
|
|||||||
if (process.env.PARSE_EXPERIMENTAL_CONFIG_ENABLED || process.env.TESTING) {
|
if (process.env.PARSE_EXPERIMENTAL_CONFIG_ENABLED || process.env.TESTING) {
|
||||||
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) => {
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -258,4 +227,4 @@ function getClassName(parseClass) {
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
ParseServer: ParseServer,
|
ParseServer: ParseServer,
|
||||||
S3Adapter: S3Adapter
|
S3Adapter: S3Adapter
|
||||||
};
|
};
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
|
import cache from './cache';
|
||||||
|
|
||||||
var Parse = require('parse/node').Parse;
|
var Parse = require('parse/node').Parse;
|
||||||
|
|
||||||
var auth = require('./Auth');
|
var auth = require('./Auth');
|
||||||
var cache = require('./cache');
|
|
||||||
var Config = require('./Config');
|
var Config = require('./Config');
|
||||||
|
|
||||||
// Checks that the request is authorized for this app and checks user
|
// Checks that the request is authorized for this app and checks user
|
||||||
|
|||||||
14
src/rest.js
14
src/rest.js
@@ -8,8 +8,8 @@
|
|||||||
// things.
|
// things.
|
||||||
|
|
||||||
var Parse = require('parse/node').Parse;
|
var Parse = require('parse/node').Parse;
|
||||||
|
import cache from './cache';
|
||||||
|
|
||||||
var cache = require('./cache');
|
|
||||||
var RestQuery = require('./RestQuery');
|
var RestQuery = require('./RestQuery');
|
||||||
var RestWrite = require('./RestWrite');
|
var RestWrite = require('./RestWrite');
|
||||||
var triggers = require('./triggers');
|
var triggers = require('./triggers');
|
||||||
@@ -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({});
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// testing-routes.js
|
// testing-routes.js
|
||||||
|
import cache from './cache';
|
||||||
|
|
||||||
var express = require('express'),
|
var express = require('express'),
|
||||||
cache = require('./cache'),
|
|
||||||
middlewares = require('./middlewares'),
|
middlewares = require('./middlewares'),
|
||||||
cryptoUtils = require('./cryptoUtils');
|
cryptoUtils = require('./cryptoUtils');
|
||||||
|
|
||||||
|
|||||||
125
src/triggers.js
125
src/triggers.js
@@ -1,28 +1,100 @@
|
|||||||
// triggers.js
|
// triggers.js
|
||||||
|
import Parse from 'parse/node';
|
||||||
|
import cache from './cache';
|
||||||
|
|
||||||
var Parse = require('parse/node').Parse;
|
export const Types = {
|
||||||
|
|
||||||
var Types = {
|
|
||||||
beforeSave: 'beforeSave',
|
beforeSave: 'beforeSave',
|
||||||
afterSave: 'afterSave',
|
afterSave: 'afterSave',
|
||||||
beforeDelete: 'beforeDelete',
|
beforeDelete: 'beforeDelete',
|
||||||
afterDelete: 'afterDelete'
|
afterDelete: 'afterDelete'
|
||||||
};
|
};
|
||||||
|
|
||||||
var getTrigger = function(className, triggerType) {
|
const baseStore = function() {
|
||||||
if (Parse.Cloud.Triggers
|
|
||||||
&& Parse.Cloud.Triggers[triggerType]
|
let Validators = {};
|
||||||
&& Parse.Cloud.Triggers[triggerType][className]) {
|
let Functions = {};
|
||||||
return Parse.Cloud.Triggers[triggerType][className];
|
let Triggers = Object.keys(Types).reduce(function(base, key){
|
||||||
|
base[key] = {};
|
||||||
|
return base;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
return Object.freeze({
|
||||||
|
Functions,
|
||||||
|
Validators,
|
||||||
|
Triggers
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const _triggerStore = {};
|
||||||
|
|
||||||
|
export function addFunction(functionName, handler, validationHandler, applicationId) {
|
||||||
|
applicationId = applicationId || Parse.applicationId;
|
||||||
|
_triggerStore[applicationId] = _triggerStore[applicationId] || baseStore();
|
||||||
|
_triggerStore[applicationId].Functions[functionName] = handler;
|
||||||
|
_triggerStore[applicationId].Validators[functionName] = validationHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addTrigger(type, className, handler, applicationId) {
|
||||||
|
applicationId = applicationId || Parse.applicationId;
|
||||||
|
_triggerStore[applicationId] = _triggerStore[applicationId] || baseStore();
|
||||||
|
_triggerStore[applicationId].Triggers[type][className] = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeFunction(functionName, applicationId) {
|
||||||
|
applicationId = applicationId || Parse.applicationId;
|
||||||
|
delete _triggerStore[applicationId].Functions[functionName]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeTrigger(type, className, applicationId) {
|
||||||
|
applicationId = applicationId || Parse.applicationId;
|
||||||
|
delete _triggerStore[applicationId].Triggers[type][className]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function _unregister(a,b,c,d) {
|
||||||
|
if (d) {
|
||||||
|
removeTrigger(c,d,a);
|
||||||
|
delete _triggerStore[a][b][c][d];
|
||||||
|
} else {
|
||||||
|
delete _triggerStore[a][b][c];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function getTrigger(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;
|
||||||
};
|
};
|
||||||
|
|
||||||
function triggerExists(className: string, type: string): boolean {
|
export function triggerExists(className: string, type: string, applicationId: string): boolean {
|
||||||
return (getTrigger(className, type) != undefined);
|
return (getTrigger(className, type, applicationId) != undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
var getRequestObject = function(triggerType, auth, parseObject, originalParseObject) {
|
export function getFunction(functionName, applicationId) {
|
||||||
|
var manager = _triggerStore[applicationId];
|
||||||
|
if (manager && manager.Functions) {
|
||||||
|
return manager.Functions[functionName];
|
||||||
|
};
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getValidator(functionName, applicationId) {
|
||||||
|
var manager = _triggerStore[applicationId];
|
||||||
|
if (manager && manager.Validators) {
|
||||||
|
return manager.Validators[functionName];
|
||||||
|
};
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getRequestObject(triggerType, auth, parseObject, originalParseObject) {
|
||||||
var request = {
|
var request = {
|
||||||
triggerName: triggerType,
|
triggerName: triggerType,
|
||||||
object: parseObject,
|
object: parseObject,
|
||||||
@@ -51,10 +123,14 @@ var getRequestObject = function(triggerType, auth, parseObject, originalParseObj
|
|||||||
// The API will call this with REST API formatted objects, this will
|
// The API will call this with REST API formatted objects, this will
|
||||||
// transform them to Parse.Object instances expected by Cloud Code.
|
// transform them to Parse.Object instances expected by Cloud Code.
|
||||||
// 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) {
|
export function getResponseObject(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,34 +148,29 @@ 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) {
|
export function maybeRunTrigger(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);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// Converts a REST-format object to a Parse.Object
|
// Converts a REST-format object to a Parse.Object
|
||||||
// data is either className or an object
|
// data is either className or an object
|
||||||
function inflate(data, restObject) {
|
export function inflate(data, restObject) {
|
||||||
var copy = typeof data == 'object' ? data : {className: data};
|
var copy = typeof data == 'object' ? data : {className: data};
|
||||||
for (var key in restObject) {
|
for (var key in restObject) {
|
||||||
copy[key] = restObject[key];
|
copy[key] = restObject[key];
|
||||||
}
|
}
|
||||||
return Parse.Object.fromJSON(copy);
|
return Parse.Object.fromJSON(copy);
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
getTrigger: getTrigger,
|
|
||||||
getRequestObject: getRequestObject,
|
|
||||||
inflate: inflate,
|
|
||||||
maybeRunTrigger: maybeRunTrigger,
|
|
||||||
triggerExists: triggerExists,
|
|
||||||
Types: Types
|
|
||||||
};
|
|
||||||
|
|||||||
Reference in New Issue
Block a user