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:
Florent Vilmart
2016-04-14 19:24:56 -04:00
parent 51970fb470
commit 1023baf20d
17 changed files with 317 additions and 291 deletions

View File

@@ -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');

View File

@@ -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({

View File

@@ -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();
}); });
}); });
}); });

View File

@@ -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();
}); });

View File

@@ -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

View File

@@ -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

View File

@@ -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;

View File

@@ -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
}; };

View File

@@ -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}`;
} }

View File

@@ -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) {

View File

@@ -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);

View File

@@ -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
);
}); });
}); });
} }

View File

@@ -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();
}) })

View File

@@ -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() {

View File

@@ -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

View File

@@ -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'

View File

@@ -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({