DBController refactoring (#1228)
* Moves transform to MongoTransform - Adds ACL query injection in MongoTransform * Removes adaptiveCollection from DatabaseController - All collections manipulations are now handled by a DBController - Adds optional flags to configure an unsafe databaseController for direct access - Adds ability to configure RestWrite with multiple writes - Moves some transfirmations to MongoTransform as they output specific code * Renames Unsafe to WithoutValidation
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
// These tests are unit tests designed to only test transform.js.
|
// These tests are unit tests designed to only test transform.js.
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
let transform = require('../src/transform');
|
let transform = require('../src/Adapters/Storage/Mongo/MongoTransform');
|
||||||
let dd = require('deep-diff');
|
let dd = require('deep-diff');
|
||||||
let mongodb = require('mongodb');
|
let mongodb = require('mongodb');
|
||||||
|
|
||||||
@@ -7,7 +7,7 @@ let Config = require('../src/Config');
|
|||||||
describe('a GlobalConfig', () => {
|
describe('a GlobalConfig', () => {
|
||||||
beforeEach(done => {
|
beforeEach(done => {
|
||||||
let config = new Config('test');
|
let config = new Config('test');
|
||||||
config.database.adaptiveCollection('_GlobalConfig')
|
config.database.adapter.adaptiveCollection('_GlobalConfig')
|
||||||
.then(coll => coll.upsertOne({ '_id': 1 }, { $set: { params: { companies: ['US', 'DK'] } } }))
|
.then(coll => coll.upsertOne({ '_id': 1 }, { $set: { params: { companies: ['US', 'DK'] } } }))
|
||||||
.then(() => { done(); });
|
.then(() => { done(); });
|
||||||
});
|
});
|
||||||
@@ -43,6 +43,35 @@ describe('a GlobalConfig', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('properly handles delete op', (done) => {
|
||||||
|
request.put({
|
||||||
|
url : 'http://localhost:8378/1/config',
|
||||||
|
json : true,
|
||||||
|
body : { params: { companies: {__op: 'Delete'}, foo: 'bar' } },
|
||||||
|
headers: {
|
||||||
|
'X-Parse-Application-Id': 'test',
|
||||||
|
'X-Parse-Master-Key' : 'test'
|
||||||
|
}
|
||||||
|
}, (error, response, body) => {
|
||||||
|
expect(response.statusCode).toEqual(200);
|
||||||
|
expect(body.result).toEqual(true);
|
||||||
|
request.get({
|
||||||
|
url : 'http://localhost:8378/1/config',
|
||||||
|
json : true,
|
||||||
|
headers: {
|
||||||
|
'X-Parse-Application-Id': 'test',
|
||||||
|
'X-Parse-Master-Key' : 'test'
|
||||||
|
}
|
||||||
|
}, (error, response, body) => {
|
||||||
|
expect(response.statusCode).toEqual(200);
|
||||||
|
expect(body.params.companies).toBeUndefined();
|
||||||
|
expect(body.params.foo).toBe('bar');
|
||||||
|
expect(Object.keys(body.params).length).toBe(1);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('fail to update if master key is missing', (done) => {
|
it('fail to update if master key is missing', (done) => {
|
||||||
request.put({
|
request.put({
|
||||||
url : 'http://localhost:8378/1/config',
|
url : 'http://localhost:8378/1/config',
|
||||||
@@ -61,7 +90,7 @@ describe('a GlobalConfig', () => {
|
|||||||
|
|
||||||
it('failed getting config when it is missing', (done) => {
|
it('failed getting config when it is missing', (done) => {
|
||||||
let config = new Config('test');
|
let config = new Config('test');
|
||||||
config.database.adaptiveCollection('_GlobalConfig')
|
config.database.adapter.adaptiveCollection('_GlobalConfig')
|
||||||
.then(coll => coll.deleteOne({ '_id': 1 }))
|
.then(coll => coll.deleteOne({ '_id': 1 }))
|
||||||
.then(() => {
|
.then(() => {
|
||||||
request.get({
|
request.get({
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ app.listen(12345);
|
|||||||
|
|
||||||
|
|
||||||
describe('Hooks', () => {
|
describe('Hooks', () => {
|
||||||
|
|
||||||
it("should have some hooks registered", (done) => {
|
it("should have some hooks registered", (done) => {
|
||||||
Parse.Hooks.getFunctions().then((res) => {
|
Parse.Hooks.getFunctions().then((res) => {
|
||||||
expect(res.constructor).toBe(Array.prototype.constructor);
|
expect(res.constructor).toBe(Array.prototype.constructor);
|
||||||
@@ -26,7 +26,7 @@ describe('Hooks', () => {
|
|||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should have some triggers registered", (done) => {
|
it("should have some triggers registered", (done) => {
|
||||||
Parse.Hooks.getTriggers().then( (res) => {
|
Parse.Hooks.getTriggers().then( (res) => {
|
||||||
expect(res.constructor).toBe(Array.prototype.constructor);
|
expect(res.constructor).toBe(Array.prototype.constructor);
|
||||||
@@ -59,7 +59,7 @@ describe('Hooks', () => {
|
|||||||
}).then((res) => {
|
}).then((res) => {
|
||||||
expect(res.functionName).toBe("My-Test-Function");
|
expect(res.functionName).toBe("My-Test-Function");
|
||||||
expect(res.url).toBe("http://anotherurl")
|
expect(res.url).toBe("http://anotherurl")
|
||||||
|
|
||||||
return Parse.Hooks.deleteFunction("My-Test-Function");
|
return Parse.Hooks.deleteFunction("My-Test-Function");
|
||||||
}, (err) => {
|
}, (err) => {
|
||||||
fail(err);
|
fail(err);
|
||||||
@@ -81,7 +81,7 @@ describe('Hooks', () => {
|
|||||||
done();
|
done();
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should CRUD a trigger registration", (done) => {
|
it("should CRUD a trigger registration", (done) => {
|
||||||
// Create
|
// Create
|
||||||
Parse.Hooks.createTrigger("MyClass","beforeDelete", "http://someurl").then((res) => {
|
Parse.Hooks.createTrigger("MyClass","beforeDelete", "http://someurl").then((res) => {
|
||||||
@@ -105,7 +105,7 @@ describe('Hooks', () => {
|
|||||||
}).then((res) => {
|
}).then((res) => {
|
||||||
expect(res.className).toBe("MyClass");
|
expect(res.className).toBe("MyClass");
|
||||||
expect(res.url).toBe("http://anotherurl")
|
expect(res.url).toBe("http://anotherurl")
|
||||||
|
|
||||||
return Parse.Hooks.deleteTrigger("MyClass","beforeDelete");
|
return Parse.Hooks.deleteTrigger("MyClass","beforeDelete");
|
||||||
}, (err) => {
|
}, (err) => {
|
||||||
fail(err);
|
fail(err);
|
||||||
@@ -127,7 +127,7 @@ describe('Hooks', () => {
|
|||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should fail to register hooks without Master Key", (done) => {
|
it("should fail to register hooks without Master Key", (done) => {
|
||||||
request.post(Parse.serverURL+"/hooks/functions", {
|
request.post(Parse.serverURL+"/hooks/functions", {
|
||||||
headers: {
|
headers: {
|
||||||
@@ -141,7 +141,7 @@ describe('Hooks', () => {
|
|||||||
done();
|
done();
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should fail trying to create two times the same function", (done) => {
|
it("should fail trying to create two times the same function", (done) => {
|
||||||
Parse.Hooks.createFunction("my_new_function", "http://url.com").then( () => {
|
Parse.Hooks.createFunction("my_new_function", "http://url.com").then( () => {
|
||||||
return Parse.Hooks.createFunction("my_new_function", "http://url.com")
|
return Parse.Hooks.createFunction("my_new_function", "http://url.com")
|
||||||
@@ -162,7 +162,7 @@ describe('Hooks', () => {
|
|||||||
done();
|
done();
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should fail trying to create two times the same trigger", (done) => {
|
it("should fail trying to create two times the same trigger", (done) => {
|
||||||
Parse.Hooks.createTrigger("MyClass", "beforeSave", "http://url.com").then( () => {
|
Parse.Hooks.createTrigger("MyClass", "beforeSave", "http://url.com").then( () => {
|
||||||
return Parse.Hooks.createTrigger("MyClass", "beforeSave", "http://url.com")
|
return Parse.Hooks.createTrigger("MyClass", "beforeSave", "http://url.com")
|
||||||
@@ -181,7 +181,7 @@ describe('Hooks', () => {
|
|||||||
done();
|
done();
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should fail trying to update a function that don't exist", (done) => {
|
it("should fail trying to update a function that don't exist", (done) => {
|
||||||
Parse.Hooks.updateFunction("A_COOL_FUNCTION", "http://url.com").then( () => {
|
Parse.Hooks.updateFunction("A_COOL_FUNCTION", "http://url.com").then( () => {
|
||||||
fail("Should not succeed")
|
fail("Should not succeed")
|
||||||
@@ -198,7 +198,7 @@ describe('Hooks', () => {
|
|||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should fail trying to update a trigger that don't exist", (done) => {
|
it("should fail trying to update a trigger that don't exist", (done) => {
|
||||||
Parse.Hooks.updateTrigger("AClassName","beforeSave", "http://url.com").then( () => {
|
Parse.Hooks.updateTrigger("AClassName","beforeSave", "http://url.com").then( () => {
|
||||||
fail("Should not succeed")
|
fail("Should not succeed")
|
||||||
@@ -215,8 +215,8 @@ describe('Hooks', () => {
|
|||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it("should fail trying to create a malformed function", (done) => {
|
it("should fail trying to create a malformed function", (done) => {
|
||||||
Parse.Hooks.createFunction("MyFunction").then( (res) => {
|
Parse.Hooks.createFunction("MyFunction").then( (res) => {
|
||||||
fail(res);
|
fail(res);
|
||||||
@@ -226,7 +226,7 @@ describe('Hooks', () => {
|
|||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should fail trying to create a malformed function (REST)", (done) => {
|
it("should fail trying to create a malformed function (REST)", (done) => {
|
||||||
request.post(Parse.serverURL+"/hooks/functions", {
|
request.post(Parse.serverURL+"/hooks/functions", {
|
||||||
headers: {
|
headers: {
|
||||||
@@ -241,16 +241,16 @@ describe('Hooks', () => {
|
|||||||
done();
|
done();
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it("should create hooks and properly preload them", (done) => {
|
it("should create hooks and properly preload them", (done) => {
|
||||||
|
|
||||||
var promises = [];
|
var promises = [];
|
||||||
for (var i = 0; i<5; i++) {
|
for (var i = 0; i<5; i++) {
|
||||||
promises.push(Parse.Hooks.createTrigger("MyClass"+i, "beforeSave", "http://url.com/beforeSave/"+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));
|
promises.push(Parse.Hooks.createFunction("AFunction"+i, "http://url.com/function"+i));
|
||||||
}
|
}
|
||||||
|
|
||||||
Parse.Promise.when(promises).then(function(results){
|
Parse.Promise.when(promises).then(function(results){
|
||||||
for (var i=0; i<5; i++) {
|
for (var i=0; i<5; i++) {
|
||||||
// Delete everything from memory, as the server just started
|
// Delete everything from memory, as the server just started
|
||||||
@@ -263,7 +263,7 @@ describe('Hooks', () => {
|
|||||||
return hooksController.load()
|
return hooksController.load()
|
||||||
}, (err) => {
|
}, (err) => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
fail();
|
fail('Should properly create all hooks');
|
||||||
done();
|
done();
|
||||||
}).then(function() {
|
}).then(function() {
|
||||||
for (var i=0; i<5; i++) {
|
for (var i=0; i<5; i++) {
|
||||||
@@ -273,17 +273,17 @@ describe('Hooks', () => {
|
|||||||
done();
|
done();
|
||||||
}, (err) => {
|
}, (err) => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
fail();
|
fail('should properly load all hooks');
|
||||||
done();
|
done();
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should run the function on the test server", (done) => {
|
it("should run the function on the test server", (done) => {
|
||||||
|
|
||||||
app.post("/SomeFunction", function(req, res) {
|
app.post("/SomeFunction", function(req, res) {
|
||||||
res.json({success:"OK!"});
|
res.json({success:"OK!"});
|
||||||
});
|
});
|
||||||
|
|
||||||
Parse.Hooks.createFunction("SOME_TEST_FUNCTION", hookServerURL+"/SomeFunction").then(function(){
|
Parse.Hooks.createFunction("SOME_TEST_FUNCTION", hookServerURL+"/SomeFunction").then(function(){
|
||||||
return Parse.Cloud.run("SOME_TEST_FUNCTION")
|
return Parse.Cloud.run("SOME_TEST_FUNCTION")
|
||||||
}, (err) => {
|
}, (err) => {
|
||||||
@@ -299,9 +299,9 @@ describe('Hooks', () => {
|
|||||||
done();
|
done();
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should run the function on the test server", (done) => {
|
it("should run the function on the test server", (done) => {
|
||||||
|
|
||||||
app.post("/SomeFunctionError", function(req, res) {
|
app.post("/SomeFunctionError", function(req, res) {
|
||||||
res.json({error: {code: 1337, error: "hacking that one!"}});
|
res.json({error: {code: 1337, error: "hacking that one!"}});
|
||||||
});
|
});
|
||||||
@@ -322,8 +322,8 @@ describe('Hooks', () => {
|
|||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it("should run the beforeSave hook on the test server", (done) => {
|
it("should run the beforeSave hook on the test server", (done) => {
|
||||||
var triggerCount = 0;
|
var triggerCount = 0;
|
||||||
app.post("/BeforeSaveSome", function(req, res) {
|
app.post("/BeforeSaveSome", function(req, res) {
|
||||||
@@ -350,7 +350,7 @@ describe('Hooks', () => {
|
|||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should run the afterSave hook on the test server", (done) => {
|
it("should run the afterSave hook on the test server", (done) => {
|
||||||
var triggerCount = 0;
|
var triggerCount = 0;
|
||||||
var newObjectId;
|
var newObjectId;
|
||||||
@@ -387,4 +387,4 @@ describe('Hooks', () => {
|
|||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -184,7 +184,6 @@ describe('PushController', () => {
|
|||||||
}).then((result) => {
|
}).then((result) => {
|
||||||
done();
|
done();
|
||||||
}, (err) => {
|
}, (err) => {
|
||||||
console.error(err);
|
|
||||||
fail("should not fail");
|
fail("should not fail");
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
@@ -233,7 +232,6 @@ describe('PushController', () => {
|
|||||||
}).then((result) => {
|
}).then((result) => {
|
||||||
done();
|
done();
|
||||||
}, (err) => {
|
}, (err) => {
|
||||||
console.error(err);
|
|
||||||
fail("should not fail");
|
fail("should not fail");
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -266,7 +266,10 @@ describe("Email Verification", () => {
|
|||||||
.then((user) => {
|
.then((user) => {
|
||||||
return user.save();
|
return user.save();
|
||||||
}).then((user) => {
|
}).then((user) => {
|
||||||
return Parse.User.requestPasswordReset("cool_guy@parse.com");
|
return Parse.User.requestPasswordReset("cool_guy@parse.com").catch((err) => {
|
||||||
|
fail('Should not fail requesting a password');
|
||||||
|
done();
|
||||||
|
})
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
expect(calls).toBe(2);
|
expect(calls).toBe(2);
|
||||||
done();
|
done();
|
||||||
@@ -551,7 +554,7 @@ describe("Password Reset", () => {
|
|||||||
Parse.User.requestPasswordReset('user@parse.com', {
|
Parse.User.requestPasswordReset('user@parse.com', {
|
||||||
error: (err) => {
|
error: (err) => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
fail("Should not fail");
|
fail("Should not fail requesting a password");
|
||||||
done();
|
done();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -628,7 +631,7 @@ describe("Password Reset", () => {
|
|||||||
|
|
||||||
Parse.User.logIn("zxcv", "hello").then(function(user){
|
Parse.User.logIn("zxcv", "hello").then(function(user){
|
||||||
let config = new Config('test');
|
let config = new Config('test');
|
||||||
config.database.adaptiveCollection('_User')
|
config.database.adapter.adaptiveCollection('_User')
|
||||||
.then(coll => coll.find({ 'username': 'zxcv' }, { limit: 1 }))
|
.then(coll => coll.find({ 'username': 'zxcv' }, { limit: 1 }))
|
||||||
.then((results) => {
|
.then((results) => {
|
||||||
// _perishable_token should be unset after reset password
|
// _perishable_token should be unset after reset password
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
|
|
||||||
import MongoCollection from './MongoCollection';
|
import MongoCollection from './MongoCollection';
|
||||||
|
import * as transform from './MongoTransform';
|
||||||
|
|
||||||
function mongoFieldToParseSchemaField(type) {
|
function mongoFieldToParseSchemaField(type) {
|
||||||
if (type[0] === '*') {
|
if (type[0] === '*') {
|
||||||
@@ -200,6 +201,10 @@ class MongoSchemaCollection {
|
|||||||
update = {'$set': update};
|
update = {'$set': update};
|
||||||
return this.upsertSchema(className, query, update);
|
return this.upsertSchema(className, query, update);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get transform() {
|
||||||
|
return transform;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exported for testing reasons and because we haven't moved all mongo schema format
|
// Exported for testing reasons and because we haven't moved all mongo schema format
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import MongoCollection from './MongoCollection';
|
import MongoCollection from './MongoCollection';
|
||||||
import MongoSchemaCollection from './MongoSchemaCollection';
|
import MongoSchemaCollection from './MongoSchemaCollection';
|
||||||
import {parse as parseUrl, format as formatUrl} from '../../../vendor/mongodbUrl';
|
import {parse as parseUrl, format as formatUrl} from '../../../vendor/mongodbUrl';
|
||||||
|
import * as transform from './MongoTransform';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
let mongodb = require('mongodb');
|
let mongodb = require('mongodb');
|
||||||
@@ -57,7 +58,7 @@ export class MongoStorageAdapter {
|
|||||||
|
|
||||||
schemaCollection() {
|
schemaCollection() {
|
||||||
return this.connect()
|
return this.connect()
|
||||||
.then(() => this.adaptiveCollection(this._collectionPrefix + MongoSchemaCollectionName))
|
.then(() => this.adaptiveCollection(MongoSchemaCollectionName))
|
||||||
.then(collection => new MongoSchemaCollection(collection));
|
.then(collection => new MongoSchemaCollection(collection));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,6 +126,10 @@ export class MongoStorageAdapter {
|
|||||||
.then(updateResult => this.schemaCollection())
|
.then(updateResult => this.schemaCollection())
|
||||||
.then(schemaCollection => schemaCollection.updateSchema(className, schemaUpdate));
|
.then(schemaCollection => schemaCollection.updateSchema(className, schemaUpdate));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get transform() {
|
||||||
|
return transform;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default MongoStorageAdapter;
|
export default MongoStorageAdapter;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import log from './logger';
|
import log from '../../../logger';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
var mongodb = require('mongodb');
|
var mongodb = require('mongodb');
|
||||||
var Parse = require('parse/node').Parse;
|
var Parse = require('parse/node').Parse;
|
||||||
@@ -185,14 +185,16 @@ export function transformKeyValue(schema, className, restKey, restValue, options
|
|||||||
// restWhere is the "where" clause in REST API form.
|
// restWhere is the "where" clause in REST API form.
|
||||||
// Returns the mongo form of the query.
|
// Returns the mongo form of the query.
|
||||||
// Throws a Parse.Error if the input query is invalid.
|
// Throws a Parse.Error if the input query is invalid.
|
||||||
function transformWhere(schema, className, restWhere) {
|
function transformWhere(schema, className, restWhere, options = {validate: true}) {
|
||||||
let mongoWhere = {};
|
let mongoWhere = {};
|
||||||
if (restWhere['ACL']) {
|
if (restWhere['ACL']) {
|
||||||
throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Cannot query on ACL.');
|
throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Cannot query on ACL.');
|
||||||
}
|
}
|
||||||
|
let transformKeyOptions = {query: true};
|
||||||
|
transformKeyOptions.validate = options.validate;
|
||||||
for (let restKey in restWhere) {
|
for (let restKey in restWhere) {
|
||||||
let out = transformKeyValue(schema, className, restKey, restWhere[restKey],
|
let out = transformKeyValue(schema, className, restKey, restWhere[restKey],
|
||||||
{query: true, validate: true});
|
transformKeyOptions);
|
||||||
mongoWhere[out.key] = out.value;
|
mongoWhere[out.key] = out.value;
|
||||||
}
|
}
|
||||||
return mongoWhere;
|
return mongoWhere;
|
||||||
@@ -767,6 +769,87 @@ function untransformObject(schema, className, mongoObject, isNestedObject = fals
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function transformSelect(selectObject, key ,objects) {
|
||||||
|
var values = [];
|
||||||
|
for (var result of objects) {
|
||||||
|
values.push(result[key]);
|
||||||
|
}
|
||||||
|
delete selectObject['$select'];
|
||||||
|
if (Array.isArray(selectObject['$in'])) {
|
||||||
|
selectObject['$in'] = selectObject['$in'].concat(values);
|
||||||
|
} else {
|
||||||
|
selectObject['$in'] = values;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function transformDontSelect(dontSelectObject, key, objects) {
|
||||||
|
var values = [];
|
||||||
|
for (var result of objects) {
|
||||||
|
values.push(result[key]);
|
||||||
|
}
|
||||||
|
delete dontSelectObject['$dontSelect'];
|
||||||
|
if (Array.isArray(dontSelectObject['$nin'])) {
|
||||||
|
dontSelectObject['$nin'] = dontSelectObject['$nin'].concat(values);
|
||||||
|
} else {
|
||||||
|
dontSelectObject['$nin'] = values;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function transformInQuery(inQueryObject, className, results) {
|
||||||
|
var values = [];
|
||||||
|
for (var result of results) {
|
||||||
|
values.push({
|
||||||
|
__type: 'Pointer',
|
||||||
|
className: className,
|
||||||
|
objectId: result.objectId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
delete inQueryObject['$inQuery'];
|
||||||
|
if (Array.isArray(inQueryObject['$in'])) {
|
||||||
|
inQueryObject['$in'] = inQueryObject['$in'].concat(values);
|
||||||
|
} else {
|
||||||
|
inQueryObject['$in'] = values;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function transformNotInQuery(notInQueryObject, className, results) {
|
||||||
|
var values = [];
|
||||||
|
for (var result of results) {
|
||||||
|
values.push({
|
||||||
|
__type: 'Pointer',
|
||||||
|
className: className,
|
||||||
|
objectId: result.objectId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
delete notInQueryObject['$notInQuery'];
|
||||||
|
if (Array.isArray(notInQueryObject['$nin'])) {
|
||||||
|
notInQueryObject['$nin'] = notInQueryObject['$nin'].concat(values);
|
||||||
|
} else {
|
||||||
|
notInQueryObject['$nin'] = values;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addWriteACL(mongoWhere, acl) {
|
||||||
|
var writePerms = [
|
||||||
|
{_wperm: {'$exists': false}}
|
||||||
|
];
|
||||||
|
for (var entry of acl) {
|
||||||
|
writePerms.push({_wperm: {'$in': [entry]}});
|
||||||
|
}
|
||||||
|
return {'$and': [mongoWhere, {'$or': writePerms}]};
|
||||||
|
}
|
||||||
|
|
||||||
|
function addReadACL(mongoWhere, acl) {
|
||||||
|
var orParts = [
|
||||||
|
{"_rperm" : { "$exists": false }},
|
||||||
|
{"_rperm" : { "$in" : ["*"]}}
|
||||||
|
];
|
||||||
|
for (var entry of acl) {
|
||||||
|
orParts.push({"_rperm" : { "$in" : [entry]}});
|
||||||
|
}
|
||||||
|
return {'$and': [mongoWhere, {'$or': orParts}]};
|
||||||
|
}
|
||||||
|
|
||||||
var DateCoder = {
|
var DateCoder = {
|
||||||
JSONToDatabase(json) {
|
JSONToDatabase(json) {
|
||||||
return new Date(json.iso);
|
return new Date(json.iso);
|
||||||
@@ -856,9 +939,15 @@ var FileCoder = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
transformKey: transformKey,
|
transformKey,
|
||||||
transformCreate: transformCreate,
|
transformCreate,
|
||||||
transformUpdate: transformUpdate,
|
transformUpdate,
|
||||||
transformWhere: transformWhere,
|
transformWhere,
|
||||||
untransformObject: untransformObject
|
transformSelect,
|
||||||
|
transformDontSelect,
|
||||||
|
transformInQuery,
|
||||||
|
transformNotInQuery,
|
||||||
|
addReadACL,
|
||||||
|
addWriteACL,
|
||||||
|
untransformObject
|
||||||
};
|
};
|
||||||
@@ -7,18 +7,27 @@ var mongodb = require('mongodb');
|
|||||||
var Parse = require('parse/node').Parse;
|
var Parse = require('parse/node').Parse;
|
||||||
|
|
||||||
var Schema = require('./../Schema');
|
var Schema = require('./../Schema');
|
||||||
var transform = require('./../transform');
|
|
||||||
const deepcopy = require('deepcopy');
|
const deepcopy = require('deepcopy');
|
||||||
|
|
||||||
function DatabaseController(adapter) {
|
function DatabaseController(adapter, { skipValidation } = {}) {
|
||||||
this.adapter = adapter;
|
this.adapter = adapter;
|
||||||
|
|
||||||
// We don't want a mutable this.schema, because then you could have
|
// We don't want a mutable this.schema, because then you could have
|
||||||
// one request that uses different schemas for different parts of
|
// one request that uses different schemas for different parts of
|
||||||
// it. Instead, use loadSchema to get a schema.
|
// it. Instead, use loadSchema to get a schema.
|
||||||
this.schemaPromise = null;
|
this.schemaPromise = null;
|
||||||
|
this.skipValidation = !!skipValidation;
|
||||||
this.connect();
|
this.connect();
|
||||||
|
|
||||||
|
Object.defineProperty(this, 'transform', {
|
||||||
|
get: function() {
|
||||||
|
return adapter.transform;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
DatabaseController.prototype.WithoutValidation = function() {
|
||||||
|
return new DatabaseController(this.adapter, {collectionPrefix: this.collectionPrefix, skipValidation: true});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Connects to the database. Returns a promise that resolves when the
|
// Connects to the database. Returns a promise that resolves when the
|
||||||
@@ -27,10 +36,6 @@ DatabaseController.prototype.connect = function() {
|
|||||||
return this.adapter.connect();
|
return this.adapter.connect();
|
||||||
};
|
};
|
||||||
|
|
||||||
DatabaseController.prototype.adaptiveCollection = function(className) {
|
|
||||||
return this.adapter.adaptiveCollection(className);
|
|
||||||
};
|
|
||||||
|
|
||||||
DatabaseController.prototype.schemaCollection = function() {
|
DatabaseController.prototype.schemaCollection = function() {
|
||||||
return this.adapter.schemaCollection();
|
return this.adapter.schemaCollection();
|
||||||
};
|
};
|
||||||
@@ -44,6 +49,9 @@ DatabaseController.prototype.dropCollection = function(className) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
DatabaseController.prototype.validateClassName = function(className) {
|
DatabaseController.prototype.validateClassName = function(className) {
|
||||||
|
if (this.skipValidation) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
if (!Schema.classNameIsValid(className)) {
|
if (!Schema.classNameIsValid(className)) {
|
||||||
const error = new Parse.Error(Parse.Error.INVALID_CLASS_NAME, 'invalid className: ' + className);
|
const error = new Parse.Error(Parse.Error.INVALID_CLASS_NAME, 'invalid className: ' + className);
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
@@ -113,7 +121,7 @@ DatabaseController.prototype.validateObject = function(className, object, query,
|
|||||||
// Filters out any data that shouldn't be on this REST-formatted object.
|
// Filters out any data that shouldn't be on this REST-formatted object.
|
||||||
DatabaseController.prototype.untransformObject = function(
|
DatabaseController.prototype.untransformObject = function(
|
||||||
schema, isMaster, aclGroup, className, mongoObject) {
|
schema, isMaster, aclGroup, className, mongoObject) {
|
||||||
var object = transform.untransformObject(schema, className, mongoObject);
|
var object = this.transform.untransformObject(schema, className, mongoObject);
|
||||||
|
|
||||||
if (className !== '_User') {
|
if (className !== '_User') {
|
||||||
return object;
|
return object;
|
||||||
@@ -137,7 +145,7 @@ DatabaseController.prototype.untransformObject = function(
|
|||||||
// acl: a list of strings. If the object to be updated has an ACL,
|
// acl: a list of strings. If the object to be updated has an ACL,
|
||||||
// one of the provided strings must provide the caller with
|
// one of the provided strings must provide the caller with
|
||||||
// write permissions.
|
// write permissions.
|
||||||
DatabaseController.prototype.update = function(className, query, update, options) {
|
DatabaseController.prototype.update = function(className, query, update, options = {}) {
|
||||||
|
|
||||||
const originalUpdate = update;
|
const originalUpdate = update;
|
||||||
// Make a copy of the object, so we don't mutate the incoming data.
|
// Make a copy of the object, so we don't mutate the incoming data.
|
||||||
@@ -158,26 +166,29 @@ DatabaseController.prototype.update = function(className, query, update, options
|
|||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
})
|
})
|
||||||
.then(() => this.handleRelationUpdates(className, query.objectId, update))
|
.then(() => this.handleRelationUpdates(className, query.objectId, update))
|
||||||
.then(() => this.adaptiveCollection(className))
|
.then(() => this.adapter.adaptiveCollection(className))
|
||||||
.then(collection => {
|
.then(collection => {
|
||||||
var mongoWhere = transform.transformWhere(schema, className, query);
|
var mongoWhere = this.transform.transformWhere(schema, className, query, {validate: !this.skipValidation});
|
||||||
if (options.acl) {
|
if (options.acl) {
|
||||||
var writePerms = [
|
mongoWhere = this.transform.addWriteACL(mongoWhere, options.acl);
|
||||||
{_wperm: {'$exists': false}}
|
}
|
||||||
];
|
mongoUpdate = this.transform.transformUpdate(schema, className, update, {validate: !this.skipValidation});
|
||||||
for (var entry of options.acl) {
|
if (options.many) {
|
||||||
writePerms.push({_wperm: {'$in': [entry]}});
|
return collection.updateMany(mongoWhere, mongoUpdate);
|
||||||
}
|
}else if (options.upsert) {
|
||||||
mongoWhere = {'$and': [mongoWhere, {'$or': writePerms}]};
|
return collection.upsertOne(mongoWhere, mongoUpdate);
|
||||||
|
} else {
|
||||||
|
return collection.findOneAndUpdate(mongoWhere, mongoUpdate);
|
||||||
}
|
}
|
||||||
mongoUpdate = transform.transformUpdate(schema, className, update);
|
|
||||||
return collection.findOneAndUpdate(mongoWhere, mongoUpdate);
|
|
||||||
})
|
})
|
||||||
.then(result => {
|
.then(result => {
|
||||||
if (!result) {
|
if (!result) {
|
||||||
return Promise.reject(new Parse.Error(Parse.Error.OBJECT_NOT_FOUND,
|
return Promise.reject(new Parse.Error(Parse.Error.OBJECT_NOT_FOUND,
|
||||||
'Object not found.'));
|
'Object not found.'));
|
||||||
}
|
}
|
||||||
|
if (this.skipValidation) {
|
||||||
|
return Promise.resolve(result);
|
||||||
|
}
|
||||||
return sanitizeDatabaseResult(originalUpdate, result);
|
return sanitizeDatabaseResult(originalUpdate, result);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -256,7 +267,7 @@ DatabaseController.prototype.addRelation = function(key, fromClassName, fromId,
|
|||||||
owningId : fromId
|
owningId : fromId
|
||||||
};
|
};
|
||||||
let className = `_Join:${key}:${fromClassName}`;
|
let className = `_Join:${key}:${fromClassName}`;
|
||||||
return this.adaptiveCollection(className).then((coll) => {
|
return this.adapter.adaptiveCollection(className).then((coll) => {
|
||||||
return coll.upsertOne(doc, doc);
|
return coll.upsertOne(doc, doc);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -270,7 +281,7 @@ DatabaseController.prototype.removeRelation = function(key, fromClassName, fromI
|
|||||||
owningId: fromId
|
owningId: fromId
|
||||||
};
|
};
|
||||||
let className = `_Join:${key}:${fromClassName}`;
|
let className = `_Join:${key}:${fromClassName}`;
|
||||||
return this.adaptiveCollection(className).then(coll => {
|
return this.adapter.adaptiveCollection(className).then(coll => {
|
||||||
return coll.deleteOne(doc);
|
return coll.deleteOne(doc);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -295,18 +306,11 @@ DatabaseController.prototype.destroy = function(className, query, options = {})
|
|||||||
}
|
}
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
})
|
})
|
||||||
.then(() => this.adaptiveCollection(className))
|
.then(() => this.adapter.adaptiveCollection(className))
|
||||||
.then(collection => {
|
.then(collection => {
|
||||||
let mongoWhere = transform.transformWhere(schema, className, query);
|
let mongoWhere = this.transform.transformWhere(schema, className, query, {validate: !this.skipValidation});
|
||||||
|
|
||||||
if (options.acl) {
|
if (options.acl) {
|
||||||
var writePerms = [
|
mongoWhere = this.transform.addWriteACL(mongoWhere, options.acl);
|
||||||
{ _wperm: { '$exists': false } }
|
|
||||||
];
|
|
||||||
for (var entry of options.acl) {
|
|
||||||
writePerms.push({ _wperm: { '$in': [entry] } });
|
|
||||||
}
|
|
||||||
mongoWhere = { '$and': [mongoWhere, { '$or': writePerms }] };
|
|
||||||
}
|
}
|
||||||
return collection.deleteMany(mongoWhere);
|
return collection.deleteMany(mongoWhere);
|
||||||
})
|
})
|
||||||
@@ -321,7 +325,7 @@ DatabaseController.prototype.destroy = function(className, query, options = {})
|
|||||||
|
|
||||||
// Inserts an object into the database.
|
// Inserts an object into the database.
|
||||||
// Returns a promise that resolves successfully iff the object saved.
|
// Returns a promise that resolves successfully iff the object saved.
|
||||||
DatabaseController.prototype.create = function(className, object, options) {
|
DatabaseController.prototype.create = function(className, object, options = {}) {
|
||||||
// Make a copy of the object, so we don't mutate the incoming data.
|
// Make a copy of the object, so we don't mutate the incoming data.
|
||||||
let originalObject = object;
|
let originalObject = object;
|
||||||
object = deepcopy(object);
|
object = deepcopy(object);
|
||||||
@@ -340,9 +344,9 @@ DatabaseController.prototype.create = function(className, object, options) {
|
|||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
})
|
})
|
||||||
.then(() => this.handleRelationUpdates(className, null, object))
|
.then(() => this.handleRelationUpdates(className, null, object))
|
||||||
.then(() => this.adaptiveCollection(className))
|
.then(() => this.adapter.adaptiveCollection(className))
|
||||||
.then(coll => {
|
.then(coll => {
|
||||||
var mongoObject = transform.transformCreate(schema, className, object);
|
var mongoObject = this.transform.transformCreate(schema, className, object);
|
||||||
return coll.insertOne(mongoObject);
|
return coll.insertOne(mongoObject);
|
||||||
})
|
})
|
||||||
.then(result => {
|
.then(result => {
|
||||||
@@ -371,7 +375,7 @@ DatabaseController.prototype.canAddField = function(schema, className, object, a
|
|||||||
// to avoid Mongo-format dependencies.
|
// to avoid Mongo-format dependencies.
|
||||||
// Returns a promise that resolves to a list of items.
|
// Returns a promise that resolves to a list of items.
|
||||||
DatabaseController.prototype.mongoFind = function(className, query, options = {}) {
|
DatabaseController.prototype.mongoFind = function(className, query, options = {}) {
|
||||||
return this.adaptiveCollection(className)
|
return this.adapter.adaptiveCollection(className)
|
||||||
.then(collection => collection.find(query, options));
|
.then(collection => collection.find(query, options));
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -404,7 +408,7 @@ function keysForQuery(query) {
|
|||||||
// Returns a promise for a list of related ids given an owning id.
|
// Returns a promise for a list of related ids given an owning id.
|
||||||
// className here is the owning className.
|
// className here is the owning className.
|
||||||
DatabaseController.prototype.relatedIds = function(className, key, owningId) {
|
DatabaseController.prototype.relatedIds = function(className, key, owningId) {
|
||||||
return this.adaptiveCollection(joinTableName(className, key))
|
return this.adapter.adaptiveCollection(joinTableName(className, key))
|
||||||
.then(coll => coll.find({owningId : owningId}))
|
.then(coll => coll.find({owningId : owningId}))
|
||||||
.then(results => results.map(r => r.relatedId));
|
.then(results => results.map(r => r.relatedId));
|
||||||
};
|
};
|
||||||
@@ -412,7 +416,7 @@ DatabaseController.prototype.relatedIds = function(className, key, owningId) {
|
|||||||
// Returns a promise for a list of owning ids given some related ids.
|
// Returns a promise for a list of owning ids given some related ids.
|
||||||
// className here is the owning className.
|
// className here is the owning className.
|
||||||
DatabaseController.prototype.owningIds = function(className, key, relatedIds) {
|
DatabaseController.prototype.owningIds = function(className, key, relatedIds) {
|
||||||
return this.adaptiveCollection(joinTableName(className, key))
|
return this.adapter.adaptiveCollection(joinTableName(className, key))
|
||||||
.then(coll => coll.find({ relatedId: { '$in': relatedIds } }))
|
.then(coll => coll.find({ relatedId: { '$in': relatedIds } }))
|
||||||
.then(results => results.map(r => r.owningId));
|
.then(results => results.map(r => r.owningId));
|
||||||
};
|
};
|
||||||
@@ -597,7 +601,7 @@ DatabaseController.prototype.find = function(className, query, options = {}) {
|
|||||||
if (options.sort) {
|
if (options.sort) {
|
||||||
mongoOptions.sort = {};
|
mongoOptions.sort = {};
|
||||||
for (let key in options.sort) {
|
for (let key in options.sort) {
|
||||||
let mongoKey = transform.transformKey(schema, className, key);
|
let mongoKey = this.transform.transformKey(schema, className, key);
|
||||||
mongoOptions.sort[mongoKey] = options.sort[key];
|
mongoOptions.sort[mongoKey] = options.sort[key];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -612,18 +616,11 @@ DatabaseController.prototype.find = function(className, query, options = {}) {
|
|||||||
})
|
})
|
||||||
.then(() => this.reduceRelationKeys(className, query))
|
.then(() => this.reduceRelationKeys(className, query))
|
||||||
.then(() => this.reduceInRelation(className, query, schema))
|
.then(() => this.reduceInRelation(className, query, schema))
|
||||||
.then(() => this.adaptiveCollection(className))
|
.then(() => this.adapter.adaptiveCollection(className))
|
||||||
.then(collection => {
|
.then(collection => {
|
||||||
let mongoWhere = transform.transformWhere(schema, className, query);
|
let mongoWhere = this.transform.transformWhere(schema, className, query);
|
||||||
if (!isMaster) {
|
if (!isMaster) {
|
||||||
let orParts = [
|
mongoWhere = this.transform.addReadACL(mongoWhere, aclGroup);
|
||||||
{"_rperm" : { "$exists": false }},
|
|
||||||
{"_rperm" : { "$in" : ["*"]}}
|
|
||||||
];
|
|
||||||
for (let acl of aclGroup) {
|
|
||||||
orParts.push({"_rperm" : { "$in" : [acl]}});
|
|
||||||
}
|
|
||||||
mongoWhere = {'$and': [mongoWhere, {'$or': orParts}]};
|
|
||||||
}
|
}
|
||||||
if (options.count) {
|
if (options.count) {
|
||||||
delete mongoOptions.limit;
|
delete mongoOptions.limit;
|
||||||
@@ -640,6 +637,25 @@ DatabaseController.prototype.find = function(className, query, options = {}) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
DatabaseController.prototype.deleteSchema = function(className) {
|
||||||
|
return this.collectionExists(className)
|
||||||
|
.then(exist => {
|
||||||
|
if (!exist) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
return this.adapter.adaptiveCollection(className)
|
||||||
|
.then(collection => {
|
||||||
|
return collection.count()
|
||||||
|
.then(count => {
|
||||||
|
if (count > 0) {
|
||||||
|
throw new Parse.Error(255, `Class ${className} is not empty, contains ${count} objects, cannot drop schema.`);
|
||||||
|
}
|
||||||
|
return collection.drop();
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
function joinTableName(className, key) {
|
function joinTableName(className, key) {
|
||||||
return `_Join:${key}:${className}`;
|
return `_Join:${key}:${className}`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ export class HooksController {
|
|||||||
constructor(applicationId:string, collectionPrefix:string = '') {
|
constructor(applicationId:string, collectionPrefix:string = '') {
|
||||||
this._applicationId = applicationId;
|
this._applicationId = applicationId;
|
||||||
this._collectionPrefix = collectionPrefix;
|
this._collectionPrefix = collectionPrefix;
|
||||||
|
this.database = DatabaseAdapter.getDatabaseConnection(this._applicationId, this._collectionPrefix).WithoutValidation();
|
||||||
}
|
}
|
||||||
|
|
||||||
load() {
|
load() {
|
||||||
@@ -26,18 +27,6 @@ export class HooksController {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getCollection() {
|
|
||||||
if (this._collection) {
|
|
||||||
return Promise.resolve(this._collection)
|
|
||||||
}
|
|
||||||
|
|
||||||
let database = DatabaseAdapter.getDatabaseConnection(this._applicationId, this._collectionPrefix);
|
|
||||||
return database.adaptiveCollection(DefaultHooksCollectionName).then(collection => {
|
|
||||||
this._collection = collection;
|
|
||||||
return collection;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
getFunction(functionName) {
|
getFunction(functionName) {
|
||||||
return this._getHooks({ functionName: functionName }, 1).then(results => results[0]);
|
return this._getHooks({ functionName: functionName }, 1).then(results => results[0]);
|
||||||
}
|
}
|
||||||
@@ -64,17 +53,13 @@ export class HooksController {
|
|||||||
return this._removeHooks({ className: className, triggerName: triggerName });
|
return this._removeHooks({ className: className, triggerName: triggerName });
|
||||||
}
|
}
|
||||||
|
|
||||||
_getHooks(query, limit) {
|
_getHooks(query = {}, limit) {
|
||||||
let options = limit ? { limit: limit } : undefined;
|
let options = limit ? { limit: limit } : undefined;
|
||||||
return this.getCollection().then(collection => collection.find(query, options));
|
return this.database.find(DefaultHooksCollectionName, query);
|
||||||
}
|
}
|
||||||
|
|
||||||
_removeHooks(query) {
|
_removeHooks(query) {
|
||||||
return this.getCollection().then(collection => {
|
return this.database.destroy(DefaultHooksCollectionName, query);
|
||||||
return collection.deleteMany(query);
|
|
||||||
}).then(() => {
|
|
||||||
return {};
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
saveHook(hook) {
|
saveHook(hook) {
|
||||||
@@ -86,11 +71,9 @@ export class HooksController {
|
|||||||
} else {
|
} else {
|
||||||
throw new Parse.Error(143, "invalid hook declaration");
|
throw new Parse.Error(143, "invalid hook declaration");
|
||||||
}
|
}
|
||||||
return this.getCollection()
|
return this.database.update(DefaultHooksCollectionName, query, hook, {upsert: true}).then(() => {
|
||||||
.then(collection => collection.upsertOne(query, hook))
|
return Promise.resolve(hook);
|
||||||
.then(() => {
|
})
|
||||||
return hook;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
addHookToTriggers(hook) {
|
addHookToTriggers(hook) {
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import AdaptableController from './AdaptableController';
|
|||||||
import { PushAdapter } from '../Adapters/Push/PushAdapter';
|
import { PushAdapter } from '../Adapters/Push/PushAdapter';
|
||||||
import deepcopy from 'deepcopy';
|
import deepcopy from 'deepcopy';
|
||||||
import RestQuery from '../RestQuery';
|
import RestQuery from '../RestQuery';
|
||||||
|
import RestWrite from '../RestWrite';
|
||||||
|
import { master } from '../Auth';
|
||||||
import pushStatusHandler from '../pushStatusHandler';
|
import pushStatusHandler from '../pushStatusHandler';
|
||||||
|
|
||||||
const FEATURE_NAME = 'push';
|
const FEATURE_NAME = 'push';
|
||||||
@@ -54,30 +56,25 @@ export class PushController extends AdaptableController {
|
|||||||
}
|
}
|
||||||
if (body.data && body.data.badge) {
|
if (body.data && body.data.badge) {
|
||||||
let badge = body.data.badge;
|
let badge = body.data.badge;
|
||||||
let op = {};
|
let restUpdate = {};
|
||||||
if (typeof badge == 'string' && badge.toLowerCase() === 'increment') {
|
if (typeof badge == 'string' && badge.toLowerCase() === 'increment') {
|
||||||
op = { $inc: { badge: 1 } }
|
restUpdate = { badge: { __op: 'Increment', amount: 1 } }
|
||||||
} else if (Number(badge)) {
|
} else if (Number(badge)) {
|
||||||
op = { $set: { badge: badge } }
|
restUpdate = { badge: badge }
|
||||||
} else {
|
} else {
|
||||||
throw "Invalid value for badge, expected number or 'Increment'";
|
throw "Invalid value for badge, expected number or 'Increment'";
|
||||||
}
|
}
|
||||||
let updateWhere = deepcopy(where);
|
let updateWhere = deepcopy(where);
|
||||||
|
|
||||||
badgeUpdate = () => {
|
badgeUpdate = () => {
|
||||||
let badgeQuery = new RestQuery(config, auth, '_Installation', updateWhere);
|
updateWhere.deviceType = 'ios';
|
||||||
return badgeQuery.buildRestWhere().then(() => {
|
// Build a real RestQuery so we can use it in RestWrite
|
||||||
let restWhere = deepcopy(badgeQuery.restWhere);
|
let restQuery = new RestQuery(config, master(config), '_Installation', updateWhere);
|
||||||
// Force iOS only devices
|
return restQuery.buildRestWhere().then(() => {
|
||||||
if (!restWhere['$and']) {
|
let write = new RestWrite(config, master(config), '_Installation', restQuery.restWhere, restUpdate);
|
||||||
restWhere['$and'] = [badgeQuery.restWhere];
|
write.runOptions.many = true;
|
||||||
}
|
return write.execute();
|
||||||
restWhere['$and'].push({
|
});
|
||||||
'deviceType': 'ios'
|
|
||||||
});
|
|
||||||
return config.database.adaptiveCollection("_Installation")
|
|
||||||
.then(coll => coll.updateMany(restWhere, op));
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let pushStatus = pushStatusHandler(config);
|
let pushStatus = pushStatusHandler(config);
|
||||||
|
|||||||
@@ -45,38 +45,29 @@ export class UserController extends AdaptableController {
|
|||||||
// TODO: Better error here.
|
// TODO: Better error here.
|
||||||
return Promise.reject();
|
return Promise.reject();
|
||||||
}
|
}
|
||||||
|
let database = this.config.database.WithoutValidation();
|
||||||
return this.config.database
|
return database.update('_User', {
|
||||||
.adaptiveCollection('_User')
|
username: username,
|
||||||
.then(collection => {
|
_email_verify_token: token
|
||||||
// Need direct database access because verification token is not a parse field
|
}, {emailVerified: true}).then(document => {
|
||||||
return collection.findOneAndUpdate({
|
if (!document) {
|
||||||
username: username,
|
return Promise.reject();
|
||||||
_email_verify_token: token
|
}
|
||||||
}, {$set: {emailVerified: true}});
|
return Promise.resolve(document);
|
||||||
})
|
});
|
||||||
.then(document => {
|
|
||||||
if (!document) {
|
|
||||||
return Promise.reject();
|
|
||||||
}
|
|
||||||
return document;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
checkResetTokenValidity(username, token) {
|
checkResetTokenValidity(username, token) {
|
||||||
return this.config.database.adaptiveCollection('_User')
|
let database = this.config.database.WithoutValidation();
|
||||||
.then(collection => {
|
return database.find('_User', {
|
||||||
return collection.find({
|
username: username,
|
||||||
username: username,
|
_perishable_token: token
|
||||||
_perishable_token: token
|
}, {limit: 1}).then(results => {
|
||||||
}, { limit: 1 });
|
if (results.length != 1) {
|
||||||
})
|
return Promise.reject();
|
||||||
.then(results => {
|
}
|
||||||
if (results.length != 1) {
|
return results[0];
|
||||||
return Promise.reject();
|
});
|
||||||
}
|
|
||||||
return results[0];
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getUserIfNeeded(user) {
|
getUserIfNeeded(user) {
|
||||||
@@ -124,15 +115,8 @@ export class UserController extends AdaptableController {
|
|||||||
|
|
||||||
setPasswordResetToken(email) {
|
setPasswordResetToken(email) {
|
||||||
let token = randomString(25);
|
let token = randomString(25);
|
||||||
return this.config.database
|
let database = this.config.database.WithoutValidation();
|
||||||
.adaptiveCollection('_User')
|
return database.update('_User', {email: email}, {_perishable_token: token});
|
||||||
.then(collection => {
|
|
||||||
// Need direct database access because verification token is not a parse field
|
|
||||||
return collection.findOneAndUpdate(
|
|
||||||
{ email: email}, // query
|
|
||||||
{ $set: { _perishable_token: token } } // update
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sendPasswordResetEmail(email) {
|
sendPasswordResetEmail(email) {
|
||||||
@@ -166,14 +150,11 @@ export class UserController extends AdaptableController {
|
|||||||
|
|
||||||
updatePassword(username, token, password, config) {
|
updatePassword(username, token, password, config) {
|
||||||
return this.checkResetTokenValidity(username, token).then((user) => {
|
return this.checkResetTokenValidity(username, token).then((user) => {
|
||||||
return updateUserPassword(user._id, password, this.config);
|
return updateUserPassword(user.objectId, password, this.config);
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
// clear reset password token
|
// clear reset password token
|
||||||
return this.config.database.adaptiveCollection('_User').then(function (collection) {
|
return this.config.database.WithoutValidation().update('_User', { username }, {
|
||||||
// Need direct database access because verification token is not a parse field
|
_perishable_token: {__op: 'Delete'}
|
||||||
return collection.findOneAndUpdate({ username: username },// query
|
|
||||||
{ $unset: { _perishable_token: null } } // update
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -213,20 +213,7 @@ RestQuery.prototype.replaceInQuery = function() {
|
|||||||
this.config, this.auth, inQueryValue.className,
|
this.config, this.auth, inQueryValue.className,
|
||||||
inQueryValue.where, additionalOptions);
|
inQueryValue.where, additionalOptions);
|
||||||
return subquery.execute().then((response) => {
|
return subquery.execute().then((response) => {
|
||||||
var values = [];
|
this.config.database.transform.transformInQuery(inQueryObject, subquery.className, response.results);
|
||||||
for (var result of response.results) {
|
|
||||||
values.push({
|
|
||||||
__type: 'Pointer',
|
|
||||||
className: subquery.className,
|
|
||||||
objectId: result.objectId
|
|
||||||
});
|
|
||||||
}
|
|
||||||
delete inQueryObject['$inQuery'];
|
|
||||||
if (Array.isArray(inQueryObject['$in'])) {
|
|
||||||
inQueryObject['$in'] = inQueryObject['$in'].concat(values);
|
|
||||||
} else {
|
|
||||||
inQueryObject['$in'] = values;
|
|
||||||
}
|
|
||||||
// Recurse to repeat
|
// Recurse to repeat
|
||||||
return this.replaceInQuery();
|
return this.replaceInQuery();
|
||||||
});
|
});
|
||||||
@@ -257,21 +244,7 @@ RestQuery.prototype.replaceNotInQuery = function() {
|
|||||||
this.config, this.auth, notInQueryValue.className,
|
this.config, this.auth, notInQueryValue.className,
|
||||||
notInQueryValue.where, additionalOptions);
|
notInQueryValue.where, additionalOptions);
|
||||||
return subquery.execute().then((response) => {
|
return subquery.execute().then((response) => {
|
||||||
var values = [];
|
this.config.database.transform.transformNotInQuery(notInQueryObject, subquery.className, response.results);
|
||||||
for (var result of response.results) {
|
|
||||||
values.push({
|
|
||||||
__type: 'Pointer',
|
|
||||||
className: subquery.className,
|
|
||||||
objectId: result.objectId
|
|
||||||
});
|
|
||||||
}
|
|
||||||
delete notInQueryObject['$notInQuery'];
|
|
||||||
if (Array.isArray(notInQueryObject['$nin'])) {
|
|
||||||
notInQueryObject['$nin'] = notInQueryObject['$nin'].concat(values);
|
|
||||||
} else {
|
|
||||||
notInQueryObject['$nin'] = values;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Recurse to repeat
|
// Recurse to repeat
|
||||||
return this.replaceNotInQuery();
|
return this.replaceNotInQuery();
|
||||||
});
|
});
|
||||||
@@ -308,17 +281,7 @@ RestQuery.prototype.replaceSelect = function() {
|
|||||||
this.config, this.auth, selectValue.query.className,
|
this.config, this.auth, selectValue.query.className,
|
||||||
selectValue.query.where, additionalOptions);
|
selectValue.query.where, additionalOptions);
|
||||||
return subquery.execute().then((response) => {
|
return subquery.execute().then((response) => {
|
||||||
var values = [];
|
this.config.database.transform.transformSelect(selectObject, selectValue.key, response.results);
|
||||||
for (var result of response.results) {
|
|
||||||
values.push(result[selectValue.key]);
|
|
||||||
}
|
|
||||||
delete selectObject['$select'];
|
|
||||||
if (Array.isArray(selectObject['$in'])) {
|
|
||||||
selectObject['$in'] = selectObject['$in'].concat(values);
|
|
||||||
} else {
|
|
||||||
selectObject['$in'] = values;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keep replacing $select clauses
|
// Keep replacing $select clauses
|
||||||
return this.replaceSelect();
|
return this.replaceSelect();
|
||||||
})
|
})
|
||||||
@@ -353,17 +316,7 @@ RestQuery.prototype.replaceDontSelect = function() {
|
|||||||
this.config, this.auth, dontSelectValue.query.className,
|
this.config, this.auth, dontSelectValue.query.className,
|
||||||
dontSelectValue.query.where, additionalOptions);
|
dontSelectValue.query.where, additionalOptions);
|
||||||
return subquery.execute().then((response) => {
|
return subquery.execute().then((response) => {
|
||||||
var values = [];
|
this.config.database.transform.transformDontSelect(dontSelectObject, dontSelectValue.key, response.results);
|
||||||
for (var result of response.results) {
|
|
||||||
values.push(result[dontSelectValue.key]);
|
|
||||||
}
|
|
||||||
delete dontSelectObject['$dontSelect'];
|
|
||||||
if (Array.isArray(dontSelectObject['$nin'])) {
|
|
||||||
dontSelectObject['$nin'] = dontSelectObject['$nin'].concat(values);
|
|
||||||
} else {
|
|
||||||
dontSelectObject['$nin'] = values;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keep replacing $dontSelect clauses
|
// Keep replacing $dontSelect clauses
|
||||||
return this.replaceDontSelect();
|
return this.replaceDontSelect();
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -5,33 +5,28 @@ import * as middleware from "../middlewares";
|
|||||||
|
|
||||||
export class GlobalConfigRouter extends PromiseRouter {
|
export class GlobalConfigRouter extends PromiseRouter {
|
||||||
getGlobalConfig(req) {
|
getGlobalConfig(req) {
|
||||||
return req.config.database.adaptiveCollection('_GlobalConfig')
|
let database = req.config.database.WithoutValidation();
|
||||||
.then(coll => coll.find({ '_id': 1 }, { limit: 1 }))
|
return database.find('_GlobalConfig', { '_id': 1 }, { limit: 1 }).then((results) => {
|
||||||
.then(results => {
|
if (results.length != 1) {
|
||||||
if (results.length != 1) {
|
// If there is no config in the database - return empty config.
|
||||||
// If there is no config in the database - return empty config.
|
return { response: { params: {} } };
|
||||||
return { response: { params: {} } };
|
}
|
||||||
}
|
let globalConfig = results[0];
|
||||||
let globalConfig = results[0];
|
return { response: { params: globalConfig.params } };
|
||||||
return { response: { params: globalConfig.params } };
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updateGlobalConfig(req) {
|
updateGlobalConfig(req) {
|
||||||
const params = req.body.params;
|
let params = req.body.params;
|
||||||
|
// Transform in dot notation to make sure it works
|
||||||
const update = Object.keys(params).reduce((acc, key) => {
|
const update = Object.keys(params).reduce((acc, key) => {
|
||||||
if(params[key] && params[key].__op && params[key].__op === "Delete") {
|
acc[`params.${key}`] = params[key];
|
||||||
if (!acc.$unset) acc.$unset = {};
|
|
||||||
acc.$unset[`params.${key}`] = "";
|
|
||||||
} else {
|
|
||||||
if (!acc.$set) acc.$set = {};
|
|
||||||
acc.$set[`params.${key}`] = params[key];
|
|
||||||
}
|
|
||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
return req.config.database.adaptiveCollection('_GlobalConfig')
|
let database = req.config.database.WithoutValidation();
|
||||||
.then(coll => coll.upsertOne({ _id: 1 }, update))
|
return database.update('_GlobalConfig', {_id: 1}, update, {upsert: true}).then(() => {
|
||||||
.then(() => ({ response: { result: true } }));
|
return Promise.resolve({ response: { result: true } });
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
mountRoutes() {
|
mountRoutes() {
|
||||||
|
|||||||
@@ -103,22 +103,7 @@ function deleteSchema(req) {
|
|||||||
throw new Parse.Error(Parse.Error.INVALID_CLASS_NAME, Schema.invalidClassNameMessage(req.params.className));
|
throw new Parse.Error(Parse.Error.INVALID_CLASS_NAME, Schema.invalidClassNameMessage(req.params.className));
|
||||||
}
|
}
|
||||||
|
|
||||||
return req.config.database.collectionExists(req.params.className)
|
return req.config.database.deleteSchema(req.params.className)
|
||||||
.then(exist => {
|
|
||||||
if (!exist) {
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
return req.config.database.adaptiveCollection(req.params.className)
|
|
||||||
.then(collection => {
|
|
||||||
return collection.count()
|
|
||||||
.then(count => {
|
|
||||||
if (count > 0) {
|
|
||||||
throw new Parse.Error(255, `Class ${req.params.className} is not empty, contains ${count} objects, cannot drop schema.`);
|
|
||||||
}
|
|
||||||
return collection.drop();
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.then(() => {
|
.then(() => {
|
||||||
// We've dropped the collection now, so delete the item from _SCHEMA
|
// We've dropped the collection now, so delete the item from _SCHEMA
|
||||||
// and clear the _Join collections
|
// and clear the _Join collections
|
||||||
|
|||||||
@@ -15,7 +15,6 @@
|
|||||||
// TODO: hide all schema logic inside the database adapter.
|
// TODO: hide all schema logic inside the database adapter.
|
||||||
|
|
||||||
const Parse = require('parse/node').Parse;
|
const Parse = require('parse/node').Parse;
|
||||||
const transform = require('./transform');
|
|
||||||
import MongoSchemaCollection from './Adapters/Storage/Mongo/MongoSchemaCollection';
|
import MongoSchemaCollection from './Adapters/Storage/Mongo/MongoSchemaCollection';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
@@ -429,7 +428,7 @@ class Schema {
|
|||||||
validateField(className, fieldName, type, freeze) {
|
validateField(className, fieldName, type, freeze) {
|
||||||
return this.reloadData().then(() => {
|
return this.reloadData().then(() => {
|
||||||
// Just to check that the fieldName is valid
|
// Just to check that the fieldName is valid
|
||||||
transform.transformKey(this, className, fieldName);
|
this._collection.transform.transformKey(this, className, fieldName);
|
||||||
|
|
||||||
if( fieldName.indexOf(".") > 0 ) {
|
if( fieldName.indexOf(".") > 0 ) {
|
||||||
// subdocument key (x.y) => ok if x is of type 'object'
|
// subdocument key (x.y) => ok if x is of type 'object'
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { md5Hash, newObjectId } from './cryptoUtils';
|
import { md5Hash, newObjectId } from './cryptoUtils';
|
||||||
import { logger } from './logger';
|
import { logger } from './logger';
|
||||||
|
|
||||||
|
const PUSH_STATUS_COLLECTION = '_PushStatus';
|
||||||
|
|
||||||
export function flatten(array) {
|
export function flatten(array) {
|
||||||
return array.reduce((memo, element) => {
|
return array.reduce((memo, element) => {
|
||||||
if (Array.isArray(element)) {
|
if (Array.isArray(element)) {
|
||||||
@@ -17,9 +19,7 @@ export default function pushStatusHandler(config) {
|
|||||||
let initialPromise;
|
let initialPromise;
|
||||||
let pushStatus;
|
let pushStatus;
|
||||||
let objectId = newObjectId();
|
let objectId = newObjectId();
|
||||||
let collection = function() {
|
let database = config.database.WithoutValidation();
|
||||||
return config.database.adaptiveCollection('_PushStatus');
|
|
||||||
}
|
|
||||||
|
|
||||||
let setInitial = function(body = {}, where, options = {source: 'rest'}) {
|
let setInitial = function(body = {}, where, options = {source: 'rest'}) {
|
||||||
let now = new Date();
|
let now = new Date();
|
||||||
@@ -41,24 +41,20 @@ export default function pushStatusHandler(config) {
|
|||||||
_wperm: [],
|
_wperm: [],
|
||||||
_rperm: []
|
_rperm: []
|
||||||
}
|
}
|
||||||
initialPromise = collection().then((collection) => {
|
|
||||||
return collection.insertOne(object);
|
return database.create(PUSH_STATUS_COLLECTION, object).then(() => {
|
||||||
}).then((res) => {
|
|
||||||
pushStatus = {
|
pushStatus = {
|
||||||
objectId
|
objectId
|
||||||
};
|
};
|
||||||
return Promise.resolve(pushStatus);
|
return Promise.resolve(pushStatus);
|
||||||
})
|
});
|
||||||
return initialPromise;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let setRunning = function(installations) {
|
let setRunning = function(installations) {
|
||||||
logger.verbose('sending push to %d installations', installations.length);
|
logger.verbose('sending push to %d installations', installations.length);
|
||||||
return initialPromise.then(() => {
|
return database.update(PUSH_STATUS_COLLECTION,
|
||||||
return collection();
|
{status:"pending", objectId: objectId},
|
||||||
}).then((collection) => {
|
{status: "running"});
|
||||||
return collection.updateOne({status:"pending", _id: objectId}, {$set: {status: "running"}});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let complete = function(results) {
|
let complete = function(results) {
|
||||||
@@ -91,11 +87,7 @@ export default function pushStatusHandler(config) {
|
|||||||
}, update);
|
}, update);
|
||||||
}
|
}
|
||||||
logger.verbose('sent push! %d success, %d failures', update.numSent, update.numFailed);
|
logger.verbose('sent push! %d success, %d failures', update.numSent, update.numFailed);
|
||||||
return initialPromise.then(() => {
|
return database.update('_PushStatus', {status:"running", objectId }, update);
|
||||||
return collection();
|
|
||||||
}).then((collection) => {
|
|
||||||
return collection.updateOne({status:"running", _id: objectId}, {$set: update});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let fail = function(err) {
|
let fail = function(err) {
|
||||||
@@ -104,11 +96,7 @@ export default function pushStatusHandler(config) {
|
|||||||
status: 'failed'
|
status: 'failed'
|
||||||
}
|
}
|
||||||
logger.error('error while sending push', err);
|
logger.error('error while sending push', err);
|
||||||
return initialPromise.then(() => {
|
return database.update('_PushStatus', { objectId }, update);
|
||||||
return collection();
|
|
||||||
}).then((collection) => {
|
|
||||||
return collection.updateOne({_id: objectId}, {$set: update});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Object.freeze({
|
return Object.freeze({
|
||||||
|
|||||||
Reference in New Issue
Block a user