2016-01-28 10:58:12 -08:00
|
|
|
|
// A database adapter that works with data exported from the hosted
|
|
|
|
|
|
// Parse database.
|
|
|
|
|
|
|
2016-03-30 19:35:54 -07:00
|
|
|
|
import intersect from 'intersect';
|
2016-04-22 18:44:03 -07:00
|
|
|
|
import _ from 'lodash';
|
2016-03-30 19:35:54 -07:00
|
|
|
|
|
2016-01-28 10:58:12 -08:00
|
|
|
|
var mongodb = require('mongodb');
|
|
|
|
|
|
var Parse = require('parse/node').Parse;
|
|
|
|
|
|
|
2016-04-18 18:59:57 -07:00
|
|
|
|
var SchemaController = require('../Controllers/SchemaController');
|
2016-03-08 16:08:41 -08:00
|
|
|
|
const deepcopy = require('deepcopy');
|
2016-01-28 10:58:12 -08:00
|
|
|
|
|
2016-04-22 18:44:03 -07:00
|
|
|
|
function addWriteACL(query, acl) {
|
|
|
|
|
|
let newQuery = _.cloneDeep(query);
|
|
|
|
|
|
//Can't be any existing '_wperm' query, we don't allow client queries on that, no need to $and
|
|
|
|
|
|
newQuery._wperm = { "$in" : [null, ...acl]};
|
|
|
|
|
|
return newQuery;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function addReadACL(query, acl) {
|
|
|
|
|
|
let newQuery = _.cloneDeep(query);
|
|
|
|
|
|
//Can't be any existing '_rperm' query, we don't allow client queries on that, no need to $and
|
|
|
|
|
|
newQuery._rperm = { "$in" : [null, "*", ...acl]};
|
|
|
|
|
|
return newQuery;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-04-14 19:24:56 -04:00
|
|
|
|
function DatabaseController(adapter, { skipValidation } = {}) {
|
2016-02-27 02:23:57 -08:00
|
|
|
|
this.adapter = adapter;
|
2016-01-28 10:58:12 -08:00
|
|
|
|
|
|
|
|
|
|
// We don't want a mutable this.schema, because then you could have
|
|
|
|
|
|
// one request that uses different schemas for different parts of
|
|
|
|
|
|
// it. Instead, use loadSchema to get a schema.
|
|
|
|
|
|
this.schemaPromise = null;
|
2016-04-14 19:24:56 -04:00
|
|
|
|
this.skipValidation = !!skipValidation;
|
2016-01-28 10:58:12 -08:00
|
|
|
|
this.connect();
|
2016-04-14 19:24:56 -04:00
|
|
|
|
|
|
|
|
|
|
Object.defineProperty(this, 'transform', {
|
|
|
|
|
|
get: function() {
|
|
|
|
|
|
return adapter.transform;
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
DatabaseController.prototype.WithoutValidation = function() {
|
|
|
|
|
|
return new DatabaseController(this.adapter, {collectionPrefix: this.collectionPrefix, skipValidation: true});
|
2016-01-28 10:58:12 -08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Connects to the database. Returns a promise that resolves when the
|
|
|
|
|
|
// connection is successful.
|
2016-02-27 02:02:33 -08:00
|
|
|
|
DatabaseController.prototype.connect = function() {
|
2016-02-27 02:37:38 -08:00
|
|
|
|
return this.adapter.connect();
|
2016-01-28 10:58:12 -08:00
|
|
|
|
};
|
|
|
|
|
|
|
2016-03-09 15:20:59 -08:00
|
|
|
|
DatabaseController.prototype.schemaCollection = function() {
|
2016-04-13 16:45:07 -07:00
|
|
|
|
return this.adapter.schemaCollection();
|
2016-03-09 15:20:59 -08:00
|
|
|
|
};
|
|
|
|
|
|
|
2016-02-29 19:41:05 -08:00
|
|
|
|
DatabaseController.prototype.collectionExists = function(className) {
|
2016-04-13 16:45:07 -07:00
|
|
|
|
return this.adapter.collectionExists(className);
|
2016-02-29 19:41:05 -08:00
|
|
|
|
};
|
|
|
|
|
|
|
2016-03-07 19:26:40 -08:00
|
|
|
|
DatabaseController.prototype.validateClassName = function(className) {
|
2016-04-14 19:24:56 -04:00
|
|
|
|
if (this.skipValidation) {
|
|
|
|
|
|
return Promise.resolve();
|
|
|
|
|
|
}
|
2016-04-18 18:59:57 -07:00
|
|
|
|
if (!SchemaController.classNameIsValid(className)) {
|
2016-03-07 19:26:40 -08:00
|
|
|
|
const error = new Parse.Error(Parse.Error.INVALID_CLASS_NAME, 'invalid className: ' + className);
|
|
|
|
|
|
return Promise.reject(error);
|
|
|
|
|
|
}
|
|
|
|
|
|
return Promise.resolve();
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2016-01-28 10:58:12 -08:00
|
|
|
|
// Returns a promise for a schema object.
|
|
|
|
|
|
// If we are provided a acceptor, then we run it on the schema.
|
|
|
|
|
|
// If the schema isn't accepted, we reload it at most once.
|
2016-04-19 14:29:05 -07:00
|
|
|
|
DatabaseController.prototype.loadSchema = function() {
|
2016-01-28 10:58:12 -08:00
|
|
|
|
|
|
|
|
|
|
if (!this.schemaPromise) {
|
2016-03-09 15:21:29 -08:00
|
|
|
|
this.schemaPromise = this.schemaCollection().then(collection => {
|
2016-01-28 10:58:12 -08:00
|
|
|
|
delete this.schemaPromise;
|
2016-04-18 18:59:57 -07:00
|
|
|
|
return SchemaController.load(collection, this.adapter);
|
2016-01-28 10:58:12 -08:00
|
|
|
|
});
|
|
|
|
|
|
}
|
2016-04-19 14:29:05 -07:00
|
|
|
|
return this.schemaPromise;
|
2016-01-28 10:58:12 -08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Returns a promise for the classname that is related to the given
|
|
|
|
|
|
// classname through the key.
|
2016-02-27 02:02:33 -08:00
|
|
|
|
// TODO: make this not in the DatabaseController interface
|
|
|
|
|
|
DatabaseController.prototype.redirectClassNameForKey = function(className, key) {
|
2016-01-28 10:58:12 -08:00
|
|
|
|
return this.loadSchema().then((schema) => {
|
|
|
|
|
|
var t = schema.getExpectedType(className, key);
|
2016-04-12 17:39:27 -04:00
|
|
|
|
if (t.type == 'Relation') {
|
|
|
|
|
|
return t.targetClass;
|
2016-01-28 10:58:12 -08:00
|
|
|
|
} else {
|
|
|
|
|
|
return className;
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Uses the schema to validate the object (REST API format).
|
|
|
|
|
|
// Returns a promise that resolves to the new schema.
|
|
|
|
|
|
// This does not update this.schema, because in a situation like a
|
|
|
|
|
|
// batch request, that could confuse other users of the schema.
|
2016-04-22 14:05:21 -07:00
|
|
|
|
DatabaseController.prototype.validateObject = function(className, object, query, { acl }) {
|
2016-03-07 23:07:24 -05:00
|
|
|
|
let schema;
|
2016-04-22 14:05:21 -07:00
|
|
|
|
let isMaster = acl === undefined;
|
|
|
|
|
|
var aclGroup = acl || [];
|
2016-03-07 23:07:24 -05:00
|
|
|
|
return this.loadSchema().then(s => {
|
|
|
|
|
|
schema = s;
|
2016-03-30 20:27:12 -04:00
|
|
|
|
if (isMaster) {
|
|
|
|
|
|
return Promise.resolve();
|
|
|
|
|
|
}
|
|
|
|
|
|
return this.canAddField(schema, className, object, aclGroup);
|
2016-03-07 23:07:24 -05:00
|
|
|
|
}).then(() => {
|
2016-02-19 13:06:02 -05:00
|
|
|
|
return schema.validateObject(className, object, query);
|
2016-01-28 10:58:12 -08:00
|
|
|
|
});
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Like transform.untransformObject but you need to provide a className.
|
|
|
|
|
|
// Filters out any data that shouldn't be on this REST-formatted object.
|
2016-02-27 02:02:33 -08:00
|
|
|
|
DatabaseController.prototype.untransformObject = function(
|
2016-01-28 10:58:12 -08:00
|
|
|
|
schema, isMaster, aclGroup, className, mongoObject) {
|
2016-04-14 19:24:56 -04:00
|
|
|
|
var object = this.transform.untransformObject(schema, className, mongoObject);
|
2016-01-28 10:58:12 -08:00
|
|
|
|
|
|
|
|
|
|
if (className !== '_User') {
|
|
|
|
|
|
return object;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-04-14 14:50:16 -07:00
|
|
|
|
delete object.sessionToken;
|
|
|
|
|
|
|
2016-01-28 10:58:12 -08:00
|
|
|
|
if (isMaster || (aclGroup.indexOf(object.objectId) > -1)) {
|
|
|
|
|
|
return object;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-04-20 15:42:18 -07:00
|
|
|
|
delete object.authData;
|
|
|
|
|
|
|
2016-01-28 10:58:12 -08:00
|
|
|
|
return object;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Runs an update on the database.
|
|
|
|
|
|
// Returns a promise for an object with the new values for field
|
|
|
|
|
|
// modifications that don't know their results ahead of time, like
|
|
|
|
|
|
// 'increment'.
|
|
|
|
|
|
// Options:
|
|
|
|
|
|
// acl: a list of strings. If the object to be updated has an ACL,
|
|
|
|
|
|
// one of the provided strings must provide the caller with
|
|
|
|
|
|
// write permissions.
|
2016-04-22 14:05:21 -07:00
|
|
|
|
DatabaseController.prototype.update = function(className, query, update, {
|
|
|
|
|
|
acl,
|
|
|
|
|
|
many,
|
|
|
|
|
|
upsert,
|
|
|
|
|
|
} = {}) {
|
2016-03-16 19:33:04 -04:00
|
|
|
|
|
|
|
|
|
|
const originalUpdate = update;
|
2016-03-08 16:08:41 -08:00
|
|
|
|
// Make a copy of the object, so we don't mutate the incoming data.
|
|
|
|
|
|
update = deepcopy(update);
|
|
|
|
|
|
|
2016-04-22 14:05:21 -07:00
|
|
|
|
var isMaster = acl === undefined;
|
|
|
|
|
|
var aclGroup = acl || [];
|
2016-04-25 22:01:50 -07:00
|
|
|
|
var mongoUpdate;
|
2016-04-19 14:29:05 -07:00
|
|
|
|
return this.loadSchema()
|
2016-04-25 22:01:50 -07:00
|
|
|
|
.then(schemaController => {
|
|
|
|
|
|
return (isMaster ? Promise.resolve() : schemaController.validatePermission(className, aclGroup, 'update'))
|
2016-03-02 00:21:55 -08:00
|
|
|
|
.then(() => this.handleRelationUpdates(className, query.objectId, update))
|
2016-04-14 19:24:56 -04:00
|
|
|
|
.then(() => this.adapter.adaptiveCollection(className))
|
2016-03-02 00:21:55 -08:00
|
|
|
|
.then(collection => {
|
2016-04-20 21:51:11 -04:00
|
|
|
|
if (!isMaster) {
|
2016-04-25 22:01:50 -07:00
|
|
|
|
query = this.addPointerPermissions(schemaController, className, 'update', query, aclGroup);
|
2016-04-20 21:51:11 -04:00
|
|
|
|
}
|
|
|
|
|
|
if (!query) {
|
|
|
|
|
|
return Promise.resolve();
|
|
|
|
|
|
}
|
2016-04-22 14:05:21 -07:00
|
|
|
|
if (acl) {
|
2016-04-22 18:44:03 -07:00
|
|
|
|
query = addWriteACL(query, acl);
|
2016-04-14 19:24:56 -04:00
|
|
|
|
}
|
2016-04-25 22:45:16 -07:00
|
|
|
|
return schemaController.getOneSchema(className)
|
|
|
|
|
|
.catch(error => {
|
|
|
|
|
|
// If the schema doesn't exist, pretend it exists with no fields. This behaviour
|
|
|
|
|
|
// will likely need revisiting.
|
|
|
|
|
|
if (error === undefined) {
|
|
|
|
|
|
return { fields: {} };
|
|
|
|
|
|
}
|
|
|
|
|
|
throw error;
|
|
|
|
|
|
})
|
|
|
|
|
|
.then(parseFormatSchema => {
|
2016-05-18 18:05:04 -07:00
|
|
|
|
this.transform.validateQuery(query);
|
|
|
|
|
|
var mongoWhere = this.transform.transformWhere(className, query, parseFormatSchema);
|
2016-04-25 22:45:16 -07:00
|
|
|
|
mongoUpdate = this.transform.transformUpdate(
|
|
|
|
|
|
schemaController,
|
|
|
|
|
|
className,
|
|
|
|
|
|
update,
|
|
|
|
|
|
{validate: !this.skipValidation}
|
|
|
|
|
|
);
|
|
|
|
|
|
if (many) {
|
|
|
|
|
|
return collection.updateMany(mongoWhere, mongoUpdate);
|
|
|
|
|
|
} else if (upsert) {
|
|
|
|
|
|
return collection.upsertOne(mongoWhere, mongoUpdate);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
return collection.findOneAndUpdate(mongoWhere, mongoUpdate);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
2016-03-02 00:21:55 -08:00
|
|
|
|
})
|
|
|
|
|
|
.then(result => {
|
|
|
|
|
|
if (!result) {
|
2016-04-25 22:01:50 -07:00
|
|
|
|
return Promise.reject(new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.'));
|
2016-01-28 10:58:12 -08:00
|
|
|
|
}
|
2016-04-14 19:24:56 -04:00
|
|
|
|
if (this.skipValidation) {
|
|
|
|
|
|
return Promise.resolve(result);
|
|
|
|
|
|
}
|
2016-03-16 23:48:52 -04:00
|
|
|
|
return sanitizeDatabaseResult(originalUpdate, result);
|
2016-03-02 00:21:55 -08:00
|
|
|
|
});
|
2016-04-25 22:01:50 -07:00
|
|
|
|
});
|
2016-01-28 10:58:12 -08:00
|
|
|
|
};
|
|
|
|
|
|
|
2016-03-16 23:48:52 -04:00
|
|
|
|
function sanitizeDatabaseResult(originalObject, result) {
|
|
|
|
|
|
let response = {};
|
|
|
|
|
|
if (!result) {
|
|
|
|
|
|
return Promise.resolve(response);
|
|
|
|
|
|
}
|
|
|
|
|
|
Object.keys(originalObject).forEach(key => {
|
|
|
|
|
|
let keyUpdate = originalObject[key];
|
|
|
|
|
|
// determine if that was an op
|
|
|
|
|
|
if (keyUpdate && typeof keyUpdate === 'object' && keyUpdate.__op
|
|
|
|
|
|
&& ['Add', 'AddUnique', 'Remove', 'Increment'].indexOf(keyUpdate.__op) > -1) {
|
|
|
|
|
|
// only valid ops that produce an actionable result
|
|
|
|
|
|
response[key] = result[key];
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
return Promise.resolve(response);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-01-28 10:58:12 -08:00
|
|
|
|
// Processes relation-updating operations from a REST-format update.
|
|
|
|
|
|
// Returns a promise that resolves successfully when these are
|
|
|
|
|
|
// processed.
|
|
|
|
|
|
// This mutates update.
|
2016-04-22 14:05:21 -07:00
|
|
|
|
DatabaseController.prototype.handleRelationUpdates = function(className, objectId, update) {
|
2016-01-28 10:58:12 -08:00
|
|
|
|
var pending = [];
|
|
|
|
|
|
var deleteMe = [];
|
|
|
|
|
|
objectId = update.objectId || objectId;
|
|
|
|
|
|
|
|
|
|
|
|
var process = (op, key) => {
|
|
|
|
|
|
if (!op) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (op.__op == 'AddRelation') {
|
|
|
|
|
|
for (var object of op.objects) {
|
|
|
|
|
|
pending.push(this.addRelation(key, className,
|
|
|
|
|
|
objectId,
|
|
|
|
|
|
object.objectId));
|
|
|
|
|
|
}
|
|
|
|
|
|
deleteMe.push(key);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (op.__op == 'RemoveRelation') {
|
|
|
|
|
|
for (var object of op.objects) {
|
|
|
|
|
|
pending.push(this.removeRelation(key, className,
|
|
|
|
|
|
objectId,
|
|
|
|
|
|
object.objectId));
|
|
|
|
|
|
}
|
|
|
|
|
|
deleteMe.push(key);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (op.__op == 'Batch') {
|
2016-02-02 10:03:54 -05:00
|
|
|
|
for (var x of op.ops) {
|
2016-01-28 10:58:12 -08:00
|
|
|
|
process(x, key);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
for (var key in update) {
|
|
|
|
|
|
process(update[key], key);
|
|
|
|
|
|
}
|
|
|
|
|
|
for (var key of deleteMe) {
|
|
|
|
|
|
delete update[key];
|
|
|
|
|
|
}
|
|
|
|
|
|
return Promise.all(pending);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Adds a relation.
|
|
|
|
|
|
// Returns a promise that resolves successfully iff the add was successful.
|
2016-03-07 19:26:40 -08:00
|
|
|
|
DatabaseController.prototype.addRelation = function(key, fromClassName, fromId, toId) {
|
|
|
|
|
|
let doc = {
|
2016-01-28 10:58:12 -08:00
|
|
|
|
relatedId: toId,
|
2016-03-07 19:26:40 -08:00
|
|
|
|
owningId : fromId
|
2016-01-28 10:58:12 -08:00
|
|
|
|
};
|
2016-03-07 19:26:40 -08:00
|
|
|
|
let className = `_Join:${key}:${fromClassName}`;
|
2016-04-14 19:24:56 -04:00
|
|
|
|
return this.adapter.adaptiveCollection(className).then((coll) => {
|
2016-03-07 19:26:40 -08:00
|
|
|
|
return coll.upsertOne(doc, doc);
|
2016-01-28 10:58:12 -08:00
|
|
|
|
});
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Removes a relation.
|
|
|
|
|
|
// Returns a promise that resolves successfully iff the remove was
|
|
|
|
|
|
// successful.
|
2016-03-07 19:26:40 -08:00
|
|
|
|
DatabaseController.prototype.removeRelation = function(key, fromClassName, fromId, toId) {
|
2016-01-28 10:58:12 -08:00
|
|
|
|
var doc = {
|
|
|
|
|
|
relatedId: toId,
|
|
|
|
|
|
owningId: fromId
|
|
|
|
|
|
};
|
2016-03-07 19:26:40 -08:00
|
|
|
|
let className = `_Join:${key}:${fromClassName}`;
|
2016-04-14 19:24:56 -04:00
|
|
|
|
return this.adapter.adaptiveCollection(className).then(coll => {
|
2016-03-07 19:26:40 -08:00
|
|
|
|
return coll.deleteOne(doc);
|
2016-01-28 10:58:12 -08:00
|
|
|
|
});
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Removes objects matches this query from the database.
|
|
|
|
|
|
// Returns a promise that resolves successfully iff the object was
|
|
|
|
|
|
// deleted.
|
|
|
|
|
|
// Options:
|
|
|
|
|
|
// acl: a list of strings. If the object to be updated has an ACL,
|
|
|
|
|
|
// one of the provided strings must provide the caller with
|
|
|
|
|
|
// write permissions.
|
2016-04-22 14:05:21 -07:00
|
|
|
|
DatabaseController.prototype.destroy = function(className, query, { acl } = {}) {
|
|
|
|
|
|
const isMaster = acl === undefined;
|
|
|
|
|
|
const aclGroup = acl || [];
|
2016-01-28 10:58:12 -08:00
|
|
|
|
|
2016-03-07 19:26:40 -08:00
|
|
|
|
return this.loadSchema()
|
2016-04-22 14:05:21 -07:00
|
|
|
|
.then(schemaController => {
|
|
|
|
|
|
return (isMaster ? Promise.resolve() : schemaController.validatePermission(className, aclGroup, 'delete'))
|
|
|
|
|
|
.then(() => {
|
2016-04-20 21:51:11 -04:00
|
|
|
|
if (!isMaster) {
|
2016-04-22 14:05:21 -07:00
|
|
|
|
query = this.addPointerPermissions(schemaController, className, 'delete', query, aclGroup);
|
2016-04-20 21:51:11 -04:00
|
|
|
|
if (!query) {
|
|
|
|
|
|
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.');
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2016-04-22 14:05:21 -07:00
|
|
|
|
// delete by query
|
2016-04-22 18:44:03 -07:00
|
|
|
|
if (acl) {
|
|
|
|
|
|
query = addWriteACL(query, acl);
|
|
|
|
|
|
}
|
2016-04-25 22:45:16 -07:00
|
|
|
|
return schemaController.getOneSchema(className)
|
|
|
|
|
|
.catch(error => {
|
|
|
|
|
|
// If the schema doesn't exist, pretend it exists with no fields. This behaviour
|
|
|
|
|
|
// will likely need revisiting.
|
|
|
|
|
|
if (error === undefined) {
|
|
|
|
|
|
return { fields: {} };
|
|
|
|
|
|
}
|
|
|
|
|
|
throw error;
|
|
|
|
|
|
})
|
2016-04-26 10:23:14 -07:00
|
|
|
|
.then(parseFormatSchema => this.adapter.deleteObjectsByQuery(className, query, !this.skipValidation, parseFormatSchema))
|
2016-04-22 14:05:21 -07:00
|
|
|
|
.catch(error => {
|
|
|
|
|
|
// When deleting sessions while changing passwords, don't throw an error if they don't have any sessions.
|
|
|
|
|
|
if (className === "_Session" && error.code === Parse.Error.OBJECT_NOT_FOUND) {
|
|
|
|
|
|
return Promise.resolve({});
|
|
|
|
|
|
}
|
|
|
|
|
|
throw error;
|
|
|
|
|
|
});
|
2016-03-07 19:26:40 -08:00
|
|
|
|
});
|
2016-04-22 14:05:21 -07:00
|
|
|
|
});
|
2016-01-28 10:58:12 -08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Inserts an object into the database.
|
|
|
|
|
|
// Returns a promise that resolves successfully iff the object saved.
|
2016-04-22 14:05:21 -07:00
|
|
|
|
DatabaseController.prototype.create = function(className, object, { acl } = {}) {
|
2016-03-08 16:08:41 -08:00
|
|
|
|
// Make a copy of the object, so we don't mutate the incoming data.
|
2016-03-16 23:48:52 -04:00
|
|
|
|
let originalObject = object;
|
2016-03-08 16:08:41 -08:00
|
|
|
|
object = deepcopy(object);
|
|
|
|
|
|
|
2016-04-22 14:05:21 -07:00
|
|
|
|
var isMaster = acl === undefined;
|
|
|
|
|
|
var aclGroup = acl || [];
|
2016-01-28 10:58:12 -08:00
|
|
|
|
|
2016-03-07 19:26:40 -08:00
|
|
|
|
return this.validateClassName(className)
|
2016-04-20 13:35:48 -07:00
|
|
|
|
.then(() => this.loadSchema())
|
|
|
|
|
|
.then(schemaController => {
|
|
|
|
|
|
return (isMaster ? Promise.resolve() : schemaController.validatePermission(className, aclGroup, 'create'))
|
2016-03-07 19:26:40 -08:00
|
|
|
|
.then(() => this.handleRelationUpdates(className, null, object))
|
2016-04-20 13:35:48 -07:00
|
|
|
|
.then(() => schemaController.enforceClassExists(className))
|
2016-04-25 21:33:11 -04:00
|
|
|
|
.then(() => schemaController.getOneSchema(className, true))
|
2016-04-20 13:35:48 -07:00
|
|
|
|
.then(schema => this.adapter.createObject(className, object, schemaController, schema))
|
|
|
|
|
|
.then(result => sanitizeDatabaseResult(originalObject, result.ops[0]));
|
|
|
|
|
|
})
|
2016-01-28 10:58:12 -08:00
|
|
|
|
};
|
|
|
|
|
|
|
2016-03-07 23:07:24 -05:00
|
|
|
|
DatabaseController.prototype.canAddField = function(schema, className, object, aclGroup) {
|
|
|
|
|
|
let classSchema = schema.data[className];
|
|
|
|
|
|
if (!classSchema) {
|
|
|
|
|
|
return Promise.resolve();
|
|
|
|
|
|
}
|
|
|
|
|
|
let fields = Object.keys(object);
|
|
|
|
|
|
let schemaFields = Object.keys(classSchema);
|
|
|
|
|
|
let newKeys = fields.filter((field) => {
|
|
|
|
|
|
return schemaFields.indexOf(field) < 0;
|
|
|
|
|
|
})
|
|
|
|
|
|
if (newKeys.length > 0) {
|
|
|
|
|
|
return schema.validatePermission(className, aclGroup, 'addField');
|
|
|
|
|
|
}
|
|
|
|
|
|
return Promise.resolve();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-01-28 10:58:12 -08:00
|
|
|
|
// Runs a mongo query on the database.
|
|
|
|
|
|
// This should only be used for testing - use 'find' for normal code
|
|
|
|
|
|
// to avoid Mongo-format dependencies.
|
|
|
|
|
|
// Returns a promise that resolves to a list of items.
|
2016-02-27 02:02:33 -08:00
|
|
|
|
DatabaseController.prototype.mongoFind = function(className, query, options = {}) {
|
2016-04-14 19:24:56 -04:00
|
|
|
|
return this.adapter.adaptiveCollection(className)
|
2016-03-01 20:04:51 -08:00
|
|
|
|
.then(collection => collection.find(query, options));
|
2016-01-28 10:58:12 -08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Deletes everything in the database matching the current collectionPrefix
|
|
|
|
|
|
// Won't delete collections in the system namespace
|
|
|
|
|
|
// Returns a promise.
|
2016-02-27 02:02:33 -08:00
|
|
|
|
DatabaseController.prototype.deleteEverything = function() {
|
2016-01-28 10:58:12 -08:00
|
|
|
|
this.schemaPromise = null;
|
2016-04-25 11:47:57 -07:00
|
|
|
|
return this.adapter.deleteAllSchemas();
|
2016-01-28 10:58:12 -08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Finds the keys in a query. Returns a Set. REST format only
|
|
|
|
|
|
function keysForQuery(query) {
|
|
|
|
|
|
var sublist = query['$and'] || query['$or'];
|
|
|
|
|
|
if (sublist) {
|
2016-03-02 14:33:51 -05:00
|
|
|
|
let answer = sublist.reduce((memo, subquery) => {
|
|
|
|
|
|
return memo.concat(keysForQuery(subquery));
|
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
|
|
return new Set(answer);
|
2016-01-28 10:58:12 -08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return new Set(Object.keys(query));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Returns a promise for a list of related ids given an owning id.
|
|
|
|
|
|
// className here is the owning className.
|
2016-02-27 02:02:33 -08:00
|
|
|
|
DatabaseController.prototype.relatedIds = function(className, key, owningId) {
|
2016-04-14 19:24:56 -04:00
|
|
|
|
return this.adapter.adaptiveCollection(joinTableName(className, key))
|
2016-03-01 20:04:51 -08:00
|
|
|
|
.then(coll => coll.find({owningId : owningId}))
|
|
|
|
|
|
.then(results => results.map(r => r.relatedId));
|
2016-01-28 10:58:12 -08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Returns a promise for a list of owning ids given some related ids.
|
|
|
|
|
|
// className here is the owning className.
|
2016-02-27 02:02:33 -08:00
|
|
|
|
DatabaseController.prototype.owningIds = function(className, key, relatedIds) {
|
2016-04-14 19:24:56 -04:00
|
|
|
|
return this.adapter.adaptiveCollection(joinTableName(className, key))
|
2016-03-01 20:04:51 -08:00
|
|
|
|
.then(coll => coll.find({ relatedId: { '$in': relatedIds } }))
|
|
|
|
|
|
.then(results => results.map(r => r.owningId));
|
2016-01-28 10:58:12 -08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Modifies query so that it no longer has $in on relation fields, or
|
|
|
|
|
|
// equal-to-pointer constraints on relation fields.
|
|
|
|
|
|
// Returns a promise that resolves when query is mutated
|
2016-02-27 02:02:33 -08:00
|
|
|
|
DatabaseController.prototype.reduceInRelation = function(className, query, schema) {
|
2016-03-07 19:26:40 -08:00
|
|
|
|
|
2016-01-28 10:58:12 -08:00
|
|
|
|
// Search for an in-relation or equal-to-relation
|
2016-03-02 12:01:49 -05:00
|
|
|
|
// Make it sequential for now, not sure of paralleization side effects
|
2016-03-02 14:33:51 -05:00
|
|
|
|
if (query['$or']) {
|
|
|
|
|
|
let ors = query['$or'];
|
|
|
|
|
|
return Promise.all(ors.map((aQuery, index) => {
|
|
|
|
|
|
return this.reduceInRelation(className, aQuery, schema).then((aQuery) => {
|
2016-03-07 19:26:40 -08:00
|
|
|
|
query['$or'][index] = aQuery;
|
2016-03-02 14:33:51 -05:00
|
|
|
|
})
|
|
|
|
|
|
}));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-03-02 20:28:00 -05:00
|
|
|
|
let promises = Object.keys(query).map((key) => {
|
2016-04-04 14:05:03 -04:00
|
|
|
|
if (query[key] && (query[key]['$in'] || query[key]['$ne'] || query[key]['$nin'] || query[key].__type == 'Pointer')) {
|
2016-03-02 20:28:00 -05:00
|
|
|
|
let t = schema.getExpectedType(className, key);
|
2016-04-12 17:39:27 -04:00
|
|
|
|
if (!t || t.type !== 'Relation') {
|
2016-03-02 20:28:00 -05:00
|
|
|
|
return Promise.resolve(query);
|
2016-01-28 10:58:12 -08:00
|
|
|
|
}
|
2016-04-12 17:39:27 -04:00
|
|
|
|
let relatedClassName = t.targetClass;
|
2016-04-04 14:05:03 -04:00
|
|
|
|
// Build the list of queries
|
|
|
|
|
|
let queries = Object.keys(query[key]).map((constraintKey) => {
|
|
|
|
|
|
let relatedIds;
|
|
|
|
|
|
let isNegation = false;
|
|
|
|
|
|
if (constraintKey === 'objectId') {
|
|
|
|
|
|
relatedIds = [query[key].objectId];
|
|
|
|
|
|
} else if (constraintKey == '$in') {
|
|
|
|
|
|
relatedIds = query[key]['$in'].map(r => r.objectId);
|
|
|
|
|
|
} else if (constraintKey == '$nin') {
|
|
|
|
|
|
isNegation = true;
|
|
|
|
|
|
relatedIds = query[key]['$nin'].map(r => r.objectId);
|
|
|
|
|
|
} else if (constraintKey == '$ne') {
|
|
|
|
|
|
isNegation = true;
|
|
|
|
|
|
relatedIds = [query[key]['$ne'].objectId];
|
|
|
|
|
|
} else {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
return {
|
|
|
|
|
|
isNegation,
|
|
|
|
|
|
relatedIds
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// remove the current queryKey as we don,t need it anymore
|
|
|
|
|
|
delete query[key];
|
|
|
|
|
|
// execute each query independnently to build the list of
|
|
|
|
|
|
// $in / $nin
|
|
|
|
|
|
let promises = queries.map((q) => {
|
|
|
|
|
|
if (!q) {
|
|
|
|
|
|
return Promise.resolve();
|
|
|
|
|
|
}
|
|
|
|
|
|
return this.owningIds(className, key, q.relatedIds).then((ids) => {
|
|
|
|
|
|
if (q.isNegation) {
|
|
|
|
|
|
this.addNotInObjectIdsIds(ids, query);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
this.addInObjectIdsIds(ids, query);
|
|
|
|
|
|
}
|
|
|
|
|
|
return Promise.resolve();
|
|
|
|
|
|
});
|
2016-01-28 10:58:12 -08:00
|
|
|
|
});
|
2016-04-04 14:05:03 -04:00
|
|
|
|
|
|
|
|
|
|
return Promise.all(promises).then(() => {
|
|
|
|
|
|
return Promise.resolve();
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2016-01-28 10:58:12 -08:00
|
|
|
|
}
|
2016-04-04 14:05:03 -04:00
|
|
|
|
return Promise.resolve();
|
2016-03-02 20:28:00 -05:00
|
|
|
|
})
|
2016-03-07 19:26:40 -08:00
|
|
|
|
|
2016-03-02 20:28:00 -05:00
|
|
|
|
return Promise.all(promises).then(() => {
|
2016-03-02 14:33:51 -05:00
|
|
|
|
return Promise.resolve(query);
|
|
|
|
|
|
})
|
2016-01-28 10:58:12 -08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Modifies query so that it no longer has $relatedTo
|
|
|
|
|
|
// Returns a promise that resolves when query is mutated
|
2016-02-27 02:02:33 -08:00
|
|
|
|
DatabaseController.prototype.reduceRelationKeys = function(className, query) {
|
2016-03-07 19:26:40 -08:00
|
|
|
|
|
2016-03-02 14:33:51 -05:00
|
|
|
|
if (query['$or']) {
|
|
|
|
|
|
return Promise.all(query['$or'].map((aQuery) => {
|
|
|
|
|
|
return this.reduceRelationKeys(className, aQuery);
|
|
|
|
|
|
}));
|
|
|
|
|
|
}
|
2016-03-07 19:26:40 -08:00
|
|
|
|
|
2016-01-28 10:58:12 -08:00
|
|
|
|
var relatedTo = query['$relatedTo'];
|
|
|
|
|
|
if (relatedTo) {
|
|
|
|
|
|
return this.relatedIds(
|
|
|
|
|
|
relatedTo.object.className,
|
|
|
|
|
|
relatedTo.key,
|
|
|
|
|
|
relatedTo.object.objectId).then((ids) => {
|
|
|
|
|
|
delete query['$relatedTo'];
|
2016-03-03 08:40:30 -05:00
|
|
|
|
this.addInObjectIdsIds(ids, query);
|
2016-01-28 10:58:12 -08:00
|
|
|
|
return this.reduceRelationKeys(className, query);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2016-03-30 19:35:54 -07:00
|
|
|
|
DatabaseController.prototype.addInObjectIdsIds = function(ids = null, query) {
|
|
|
|
|
|
let idsFromString = typeof query.objectId === 'string' ? [query.objectId] : null;
|
|
|
|
|
|
let idsFromEq = query.objectId && query.objectId['$eq'] ? [query.objectId['$eq']] : null;
|
|
|
|
|
|
let idsFromIn = query.objectId && query.objectId['$in'] ? query.objectId['$in'] : null;
|
|
|
|
|
|
|
|
|
|
|
|
let allIds = [idsFromString, idsFromEq, idsFromIn, ids].filter(list => list !== null);
|
|
|
|
|
|
let totalLength = allIds.reduce((memo, list) => memo + list.length, 0);
|
|
|
|
|
|
|
|
|
|
|
|
let idsIntersection = [];
|
|
|
|
|
|
if (totalLength > 125) {
|
|
|
|
|
|
idsIntersection = intersect.big(allIds);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
idsIntersection = intersect(allIds);
|
2016-03-03 08:40:30 -05:00
|
|
|
|
}
|
2016-03-30 19:35:54 -07:00
|
|
|
|
|
2016-03-30 19:48:33 -07:00
|
|
|
|
// Need to make sure we don't clobber existing $lt or other constraints on objectId.
|
|
|
|
|
|
// Clobbering $eq, $in and shorthand $eq (query.objectId === 'string') constraints
|
|
|
|
|
|
// is expected though.
|
2016-03-30 19:35:54 -07:00
|
|
|
|
if (!('objectId' in query) || typeof query.objectId === 'string') {
|
|
|
|
|
|
query.objectId = {};
|
|
|
|
|
|
}
|
|
|
|
|
|
query.objectId['$in'] = idsIntersection;
|
2016-03-07 08:26:35 -05:00
|
|
|
|
|
|
|
|
|
|
return query;
|
2016-03-03 08:40:30 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-04-04 14:05:03 -04:00
|
|
|
|
DatabaseController.prototype.addNotInObjectIdsIds = function(ids = null, query) {
|
|
|
|
|
|
let idsFromNin = query.objectId && query.objectId['$nin'] ? query.objectId['$nin'] : null;
|
|
|
|
|
|
let allIds = [idsFromNin, ids].filter(list => list !== null);
|
|
|
|
|
|
let totalLength = allIds.reduce((memo, list) => memo + list.length, 0);
|
|
|
|
|
|
|
|
|
|
|
|
let idsIntersection = [];
|
|
|
|
|
|
if (totalLength > 125) {
|
|
|
|
|
|
idsIntersection = intersect.big(allIds);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
idsIntersection = intersect(allIds);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Need to make sure we don't clobber existing $lt or other constraints on objectId.
|
|
|
|
|
|
// Clobbering $eq, $in and shorthand $eq (query.objectId === 'string') constraints
|
|
|
|
|
|
// is expected though.
|
|
|
|
|
|
if (!('objectId' in query) || typeof query.objectId === 'string') {
|
|
|
|
|
|
query.objectId = {};
|
|
|
|
|
|
}
|
|
|
|
|
|
query.objectId['$nin'] = idsIntersection;
|
|
|
|
|
|
|
|
|
|
|
|
return query;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-01-28 10:58:12 -08:00
|
|
|
|
// Runs a query on the database.
|
|
|
|
|
|
// Returns a promise that resolves to a list of items.
|
|
|
|
|
|
// Options:
|
|
|
|
|
|
// skip number of results to skip.
|
|
|
|
|
|
// limit limit to this number of results.
|
|
|
|
|
|
// sort an object where keys are the fields to sort by.
|
|
|
|
|
|
// the value is +1 for ascending, -1 for descending.
|
|
|
|
|
|
// count run a count instead of returning results.
|
|
|
|
|
|
// acl restrict this operation with an ACL for the provided array
|
|
|
|
|
|
// of user objectIds and roles. acl: null means no user.
|
|
|
|
|
|
// when this field is not present, don't do anything regarding ACLs.
|
|
|
|
|
|
// TODO: make userIds not needed here. The db adapter shouldn't know
|
|
|
|
|
|
// anything about users, ideally. Then, improve the format of the ACL
|
|
|
|
|
|
// arg to work like the others.
|
2016-04-22 14:05:21 -07:00
|
|
|
|
DatabaseController.prototype.find = function(className, query, {
|
|
|
|
|
|
skip,
|
|
|
|
|
|
limit,
|
|
|
|
|
|
acl,
|
|
|
|
|
|
sort,
|
|
|
|
|
|
count,
|
|
|
|
|
|
} = {}) {
|
2016-03-30 19:35:54 -07:00
|
|
|
|
let mongoOptions = {};
|
2016-04-22 14:05:21 -07:00
|
|
|
|
if (skip) {
|
|
|
|
|
|
mongoOptions.skip = skip;
|
2016-01-28 10:58:12 -08:00
|
|
|
|
}
|
2016-04-22 14:05:21 -07:00
|
|
|
|
if (limit) {
|
|
|
|
|
|
mongoOptions.limit = limit;
|
2016-01-28 10:58:12 -08:00
|
|
|
|
}
|
2016-04-22 14:05:21 -07:00
|
|
|
|
let isMaster = acl === undefined;
|
|
|
|
|
|
let aclGroup = acl || [];
|
2016-04-25 22:12:03 -07:00
|
|
|
|
let op = typeof query.objectId == 'string' && Object.keys(query).length === 1 ? 'get' : 'find';
|
|
|
|
|
|
return this.loadSchema()
|
|
|
|
|
|
.then(schemaController => {
|
2016-05-18 18:14:54 -07:00
|
|
|
|
return schemaController.getOneSchema(className)
|
|
|
|
|
|
.catch(error => {
|
|
|
|
|
|
// If the schema doesn't exist, pretend it exists with no fields. This behaviour
|
|
|
|
|
|
// will likely need revisiting.
|
|
|
|
|
|
if (error === undefined) {
|
|
|
|
|
|
return { fields: {} };
|
|
|
|
|
|
}
|
|
|
|
|
|
throw error;
|
|
|
|
|
|
})
|
|
|
|
|
|
.then(schema => {
|
|
|
|
|
|
if (sort) {
|
|
|
|
|
|
mongoOptions.sort = {};
|
|
|
|
|
|
for (let fieldName in sort) {
|
|
|
|
|
|
// Parse.com treats queries on _created_at and _updated_at as if they were queries on createdAt and updatedAt,
|
|
|
|
|
|
// so duplicate that behaviour here.
|
|
|
|
|
|
if (fieldName === '_created_at') {
|
|
|
|
|
|
fieldName = 'createdAt';
|
|
|
|
|
|
sort['createdAt'] = sort['_created_at'];
|
|
|
|
|
|
} else if (fieldName === '_updated_at') {
|
|
|
|
|
|
fieldName = 'updatedAt';
|
|
|
|
|
|
sort['updatedAt'] = sort['_updated_at'];
|
|
|
|
|
|
}
|
2016-05-13 15:28:14 -07:00
|
|
|
|
|
2016-05-18 18:14:54 -07:00
|
|
|
|
if (!SchemaController.fieldNameIsValid(fieldName)) {
|
|
|
|
|
|
throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, `Invalid field name: ${fieldName}.`);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (fieldName.match(/^authData\.([a-zA-Z0-9_]+)\.id$/)) {
|
|
|
|
|
|
throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, `Cannot sort by ${fieldName}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
const mongoKey = this.transform.transformKey(className, fieldName, schema);
|
|
|
|
|
|
mongoOptions.sort[mongoKey] = sort[fieldName];
|
2016-05-13 15:28:14 -07:00
|
|
|
|
}
|
2016-01-28 10:58:12 -08:00
|
|
|
|
}
|
2016-05-18 18:14:54 -07:00
|
|
|
|
return (isMaster ? Promise.resolve() : schemaController.validatePermission(className, aclGroup, op))
|
|
|
|
|
|
.then(() => this.reduceRelationKeys(className, query))
|
|
|
|
|
|
.then(() => this.reduceInRelation(className, query, schemaController))
|
|
|
|
|
|
.then(() => this.adapter.adaptiveCollection(className))
|
|
|
|
|
|
.then(collection => {
|
|
|
|
|
|
if (!isMaster) {
|
|
|
|
|
|
query = this.addPointerPermissions(schemaController, className, op, query, aclGroup);
|
2016-04-25 22:12:03 -07:00
|
|
|
|
}
|
2016-05-18 18:14:54 -07:00
|
|
|
|
if (!query) {
|
|
|
|
|
|
if (op == 'get') {
|
|
|
|
|
|
return Promise.reject(new Parse.Error(Parse.Error.OBJECT_NOT_FOUND,
|
|
|
|
|
|
'Object not found.'));
|
|
|
|
|
|
} else {
|
|
|
|
|
|
return Promise.resolve([]);
|
|
|
|
|
|
}
|
2016-04-25 22:45:16 -07:00
|
|
|
|
}
|
2016-05-18 18:14:54 -07:00
|
|
|
|
if (!isMaster) {
|
|
|
|
|
|
query = addReadACL(query, aclGroup);
|
|
|
|
|
|
}
|
2016-05-18 18:05:04 -07:00
|
|
|
|
this.transform.validateQuery(query);
|
|
|
|
|
|
let mongoWhere = this.transform.transformWhere(className, query, schema);
|
2016-04-25 22:45:16 -07:00
|
|
|
|
if (count) {
|
|
|
|
|
|
delete mongoOptions.limit;
|
|
|
|
|
|
return collection.count(mongoWhere, mongoOptions);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
return collection.find(mongoWhere, mongoOptions)
|
2016-05-18 18:14:54 -07:00
|
|
|
|
.then(mongoResults => {
|
|
|
|
|
|
return mongoResults.map(result => {
|
|
|
|
|
|
return this.untransformObject(schemaController, isMaster, aclGroup, className, result);
|
2016-04-25 22:45:16 -07:00
|
|
|
|
});
|
2016-01-28 10:58:12 -08:00
|
|
|
|
});
|
2016-04-25 22:45:16 -07:00
|
|
|
|
}
|
|
|
|
|
|
});
|
2016-04-25 22:12:03 -07:00
|
|
|
|
});
|
2016-01-28 10:58:12 -08:00
|
|
|
|
});
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2016-04-14 19:24:56 -04:00
|
|
|
|
DatabaseController.prototype.deleteSchema = function(className) {
|
|
|
|
|
|
return this.collectionExists(className)
|
2016-04-25 11:47:57 -07:00
|
|
|
|
.then(exist => {
|
|
|
|
|
|
if (!exist) {
|
|
|
|
|
|
return Promise.resolve();
|
|
|
|
|
|
}
|
|
|
|
|
|
return this.adapter.adaptiveCollection(className)
|
|
|
|
|
|
.then(collection => collection.count())
|
|
|
|
|
|
.then(count => {
|
|
|
|
|
|
if (count > 0) {
|
|
|
|
|
|
throw new Parse.Error(255, `Class ${className} is not empty, contains ${count} objects, cannot drop schema.`);
|
2016-04-14 19:24:56 -04:00
|
|
|
|
}
|
2016-04-25 11:47:57 -07:00
|
|
|
|
return this.adapter.deleteOneSchema(className);
|
2016-04-14 19:24:56 -04:00
|
|
|
|
})
|
2016-04-25 11:47:57 -07:00
|
|
|
|
});
|
2016-04-14 19:24:56 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-04-20 21:51:11 -04:00
|
|
|
|
DatabaseController.prototype.addPointerPermissions = function(schema, className, operation, query, aclGroup = []) {
|
|
|
|
|
|
let perms = schema.perms[className];
|
|
|
|
|
|
let field = ['get', 'find'].indexOf(operation) > -1 ? 'readUserFields' : 'writeUserFields';
|
|
|
|
|
|
let userACL = aclGroup.filter((acl) => {
|
|
|
|
|
|
return acl.indexOf('role:') != 0 && acl != '*';
|
|
|
|
|
|
});
|
|
|
|
|
|
// the ACL should have exactly 1 user
|
|
|
|
|
|
if (perms && perms[field] && perms[field].length > 0) {
|
|
|
|
|
|
// No user set return undefined
|
|
|
|
|
|
if (userACL.length != 1) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
let userId = userACL[0];
|
|
|
|
|
|
let userPointer = {
|
|
|
|
|
|
"__type": "Pointer",
|
|
|
|
|
|
"className": "_User",
|
|
|
|
|
|
"objectId": userId
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
let constraints = {};
|
|
|
|
|
|
let permFields = perms[field];
|
|
|
|
|
|
let ors = permFields.map((key) => {
|
|
|
|
|
|
let q = {
|
|
|
|
|
|
[key]: userPointer
|
|
|
|
|
|
};
|
|
|
|
|
|
return {'$and': [q, query]};
|
|
|
|
|
|
});
|
|
|
|
|
|
if (ors.length > 1) {
|
|
|
|
|
|
return {'$or': ors};
|
|
|
|
|
|
}
|
|
|
|
|
|
return ors[0];
|
|
|
|
|
|
} else {
|
|
|
|
|
|
return query;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-03-01 20:04:51 -08:00
|
|
|
|
function joinTableName(className, key) {
|
|
|
|
|
|
return `_Join:${key}:${className}`;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-02-27 02:02:33 -08:00
|
|
|
|
module.exports = DatabaseController;
|