BVB Source Codes

parse-server Show DatabaseController.js Source code

Return Download parse-server: download DatabaseController.js Source code - Download parse-server Source code - Type:.js
  1. ?/ A database adapter that works with data exported from the hosted
  2. // Parse database.
  3.  
  4. import { Parse }              from 'parse/node';
  5. import _                      from 'lodash';
  6. import intersect              from 'intersect';
  7. import deepcopy               from 'deepcopy';
  8. import logger                 from '../logger';
  9. import * as SchemaController  from './SchemaController';
  10.  
  11. function addWriteACL(query, acl) {
  12.   const newQuery = _.cloneDeep(query);
  13.   //Can't be any existing '_wperm' query, we don't allow client queries on that, no need to $and
  14.   newQuery._wperm = { "$in" : [null, ...acl]};
  15.   return newQuery;
  16. }
  17.  
  18. function addReadACL(query, acl) {
  19.   const newQuery = _.cloneDeep(query);
  20.   //Can't be any existing '_rperm' query, we don't allow client queries on that, no need to $and
  21.   newQuery._rperm = {"$in": [null, "*", ...acl]};
  22.   return newQuery;
  23. }
  24.  
  25. // Transforms a REST API formatted ACL object to our two-field mongo format.
  26. const transformObjectACL = ({ ACL, ...result }) => {
  27.   if (!ACL) {
  28.     return result;
  29.   }
  30.  
  31.   result._wperm = [];
  32.   result._rperm = [];
  33.  
  34.   for (const entry in ACL) {
  35.     if (ACL[entry].read) {
  36.       result._rperm.push(entry);
  37.     }
  38.     if (ACL[entry].write) {
  39.       result._wperm.push(entry);
  40.     }
  41.   }
  42.   return result;
  43. }
  44.  
  45. const specialQuerykeys = ['$and', '$or', '_rperm', '_wperm', '_perishable_token', '_email_verify_token', '_email_verify_token_expires_at', '_account_lockout_expires_at', '_failed_login_count'];
  46.  
  47. const isSpecialQueryKey = key => {
  48.   return specialQuerykeys.indexOf(key) >= 0;
  49. }
  50.  
  51. const validateQuery = query => {
  52.   if (query.ACL) {
  53.     throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Cannot query on ACL.');
  54.   }
  55.  
  56.   if (query.$or) {
  57.     if (query.$or instanceof Array) {
  58.       query.$or.forEach(validateQuery);
  59.  
  60.       /* In MongoDB, $or queries which are not alone at the top level of the
  61.        * query can not make efficient use of indexes due to a long standing
  62.        * bug known as SERVER-13732.
  63.        *
  64.        * This block restructures queries in which $or is not the sole top
  65.        * level element by moving all other top-level predicates inside every
  66.        * subdocument of the $or predicate, allowing MongoDB's query planner
  67.        * to make full use of the most relevant indexes.
  68.        *
  69.        * EG:      {$or: [{a: 1}, {a: 2}], b: 2}
  70.        * Becomes: {$or: [{a: 1, b: 2}, {a: 2, b: 2}]}
  71.        *
  72.        * The only exceptions are $near and $nearSphere operators, which are
  73.        * constrained to only 1 operator per query. As a result, these ops
  74.        * remain at the top level
  75.        *
  76.        * https://jira.mongodb.org/browse/SERVER-13732
  77.        * https://github.com/parse-community/parse-server/issues/3767
  78.        */
  79.       Object.keys(query).forEach(key => {
  80.         const noCollisions = !query.$or.some(subq => subq.hasOwnProperty(key))
  81.         let hasNears = false
  82.         if (query[key] != null && typeof query[key] == 'object') {
  83.           hasNears = ('$near' in query[key] || '$nearSphere' in query[key])
  84.         }
  85.         if (key != '$or' && noCollisions && !hasNears) {
  86.           query.$or.forEach(subquery => {
  87.             subquery[key] = query[key];
  88.           });
  89.           delete query[key];
  90.         }
  91.       });
  92.       query.$or.forEach(validateQuery);
  93.     } else {
  94.       throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Bad $or format - use an array value.');
  95.     }
  96.   }
  97.  
  98.   if (query.$and) {
  99.     if (query.$and instanceof Array) {
  100.       query.$and.forEach(validateQuery);
  101.     } else {
  102.       throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Bad $and format - use an array value.');
  103.     }
  104.   }
  105.  
  106.   Object.keys(query).forEach(key => {
  107.     if (query && query[key] && query[key].$regex) {
  108.       if (typeof query[key].$options === 'string') {
  109.         if (!query[key].$options.match(/^[imxs]+$/)) {
  110.           throw new Parse.Error(Parse.Error.INVALID_QUERY, `Bad $options value for query: ${query[key].$options}`);
  111.         }
  112.       }
  113.     }
  114.     if (!isSpecialQueryKey(key) && !key.match(/^[a-zA-Z][a-zA-Z0-9_\.]*$/)) {
  115.       throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, `Invalid key name: ${key}`);
  116.     }
  117.   });
  118. }
  119.  
  120. function DatabaseController(adapter, schemaCache) {
  121.   this.adapter = adapter;
  122.   this.schemaCache = schemaCache;
  123.   // We don't want a mutable this.schema, because then you could have
  124.   // one request that uses different schemas for different parts of
  125.   // it. Instead, use loadSchema to get a schema.
  126.   this.schemaPromise = null;
  127. }
  128.  
  129. DatabaseController.prototype.collectionExists = function(className) {
  130.   return this.adapter.classExists(className);
  131. };
  132.  
  133. DatabaseController.prototype.purgeCollection = function(className) {
  134.   return this.loadSchema()
  135.   .then(schemaController => schemaController.getOneSchema(className))
  136.   .then(schema => this.adapter.deleteObjectsByQuery(className, schema, {}));
  137. };
  138.  
  139. DatabaseController.prototype.validateClassName = function(className) {
  140.   if (!SchemaController.classNameIsValid(className)) {
  141.     return Promise.reject(new Parse.Error(Parse.Error.INVALID_CLASS_NAME, 'invalid className: ' + className));
  142.   }
  143.   return Promise.resolve();
  144. };
  145.  
  146. // Returns a promise for a schemaController.
  147. DatabaseController.prototype.loadSchema = function(options = {clearCache: false}) {
  148.   if (!this.schemaPromise) {
  149.     this.schemaPromise = SchemaController.load(this.adapter, this.schemaCache, options);
  150.     this.schemaPromise.then(() => delete this.schemaPromise,
  151.                              () => delete this.schemaPromise);
  152.   }
  153.   return this.schemaPromise;
  154. };
  155.  
  156. // Returns a promise for the classname that is related to the given
  157. // classname through the key.
  158. // TODO: make this not in the DatabaseController interface
  159. DatabaseController.prototype.redirectClassNameForKey = function(className, key) {
  160.   return this.loadSchema().then((schema) => {
  161.     var t = schema.getExpectedType(className, key);
  162.     if (t && t.type == 'Relation') {
  163.       return t.targetClass;
  164.     } else {
  165.       return className;
  166.     }
  167.   });
  168. };
  169.  
  170. // Uses the schema to validate the object (REST API format).
  171. // Returns a promise that resolves to the new schema.
  172. // This does not update this.schema, because in a situation like a
  173. // batch request, that could confuse other users of the schema.
  174. DatabaseController.prototype.validateObject = function(className, object, query, { acl }) {
  175.   let schema;
  176.   const isMaster = acl === undefined;
  177.   var aclGroup = acl || [];
  178.   return this.loadSchema().then(s => {
  179.     schema = s;
  180.     if (isMaster) {
  181.       return Promise.resolve();
  182.     }
  183.     return this.canAddField(schema, className, object, aclGroup);
  184.   }).then(() => {
  185.     return schema.validateObject(className, object, query);
  186.   });
  187. };
  188.  
  189. // Filters out any data that shouldn't be on this REST-formatted object.
  190. const filterSensitiveData = (isMaster, aclGroup, className, object) => {
  191.   if (className !== '_User') {
  192.     return object;
  193.   }
  194.  
  195.   object.password = object._hashed_password;
  196.   delete object._hashed_password;
  197.  
  198.   delete object.sessionToken;
  199.  
  200.   if (isMaster) {
  201.     return object;
  202.   }
  203.   delete object._email_verify_token;
  204.   delete object._perishable_token;
  205.   delete object._perishable_token_expires_at;
  206.   delete object._tombstone;
  207.   delete object._email_verify_token_expires_at;
  208.   delete object._failed_login_count;
  209.   delete object._account_lockout_expires_at;
  210.   delete object._password_changed_at;
  211.  
  212.   if ((aclGroup.indexOf(object.objectId) > -1)) {
  213.     return object;
  214.   }
  215.   delete object.authData;
  216.   return object;
  217. };
  218.  
  219. // Runs an update on the database.
  220. // Returns a promise for an object with the new values for field
  221. // modifications that don't know their results ahead of time, like
  222. // 'increment'.
  223. // Options:
  224. //   acl:  a list of strings. If the object to be updated has an ACL,
  225. //         one of the provided strings must provide the caller with
  226. //         write permissions.
  227. const specialKeysForUpdate = ['_hashed_password', '_perishable_token', '_email_verify_token', '_email_verify_token_expires_at', '_account_lockout_expires_at', '_failed_login_count', '_perishable_token_expires_at', '_password_changed_at', '_password_history'];
  228.  
  229. const isSpecialUpdateKey = key => {
  230.   return specialKeysForUpdate.indexOf(key) >= 0;
  231. }
  232.  
  233. DatabaseController.prototype.update = function(className, query, update, {
  234.   acl,
  235.   many,
  236.   upsert,
  237. } = {}, skipSanitization = false) {
  238.   const originalQuery = query;
  239.   const originalUpdate = update;
  240.   // Make a copy of the object, so we don't mutate the incoming data.
  241.   update = deepcopy(update);
  242.   var relationUpdates = [];
  243.   var isMaster = acl === undefined;
  244.   var aclGroup = acl || [];
  245.   return this.loadSchema()
  246.   .then(schemaController => {
  247.     return (isMaster ? Promise.resolve() : schemaController.validatePermission(className, aclGroup, 'update'))
  248.     .then(() => {
  249.       relationUpdates = this.collectRelationUpdates(className, originalQuery.objectId, update);
  250.       if (!isMaster) {
  251.         query = this.addPointerPermissions(schemaController, className, 'update', query, aclGroup);
  252.       }
  253.       if (!query) {
  254.         return Promise.resolve();
  255.       }
  256.       if (acl) {
  257.         query = addWriteACL(query, acl);
  258.       }
  259.       validateQuery(query);
  260.       return schemaController.getOneSchema(className, true)
  261.       .catch(error => {
  262.         // If the schema doesn't exist, pretend it exists with no fields. This behaviour
  263.         // will likely need revisiting.
  264.         if (error === undefined) {
  265.           return { fields: {} };
  266.         }
  267.         throw error;
  268.       })
  269.       .then(schema => {
  270.         Object.keys(update).forEach(fieldName => {
  271.           if (fieldName.match(/^authData\.([a-zA-Z0-9_]+)\.id$/)) {
  272.             throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, `Invalid field name for update: ${fieldName}`);
  273.           }
  274.           fieldName = fieldName.split('.')[0];
  275.           if (!SchemaController.fieldNameIsValid(fieldName) && !isSpecialUpdateKey(fieldName)) {
  276.             throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, `Invalid field name for update: ${fieldName}`);
  277.           }
  278.         });
  279.         for (const updateOperation in update) {
  280.           if (Object.keys(updateOperation).some(innerKey => innerKey.includes('$') || innerKey.includes('.'))) {
  281.             throw new Parse.Error(Parse.Error.INVALID_NESTED_KEY, "Nested keys should not contain the '$' or '.' characters");
  282.           }
  283.         }
  284.         update = transformObjectACL(update);
  285.         transformAuthData(className, update, schema);
  286.         if (many) {
  287.           return this.adapter.updateObjectsByQuery(className, schema, query, update);
  288.         } else if (upsert) {
  289.           return this.adapter.upsertOneObject(className, schema, query, update);
  290.         } else {
  291.           return this.adapter.findOneAndUpdate(className, schema, query, update)
  292.         }
  293.       });
  294.     })
  295.     .then(result => {
  296.       if (!result) {
  297.         return Promise.reject(new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.'));
  298.       }
  299.       return this.handleRelationUpdates(className, originalQuery.objectId, update, relationUpdates).then(() => {
  300.         return result;
  301.       });
  302.     }).then((result) => {
  303.       if (skipSanitization) {
  304.         return Promise.resolve(result);
  305.       }
  306.       return sanitizeDatabaseResult(originalUpdate, result);
  307.     });
  308.   });
  309. };
  310.  
  311. function sanitizeDatabaseResult(originalObject, result) {
  312.   const response = {};
  313.   if (!result) {
  314.     return Promise.resolve(response);
  315.   }
  316.   Object.keys(originalObject).forEach(key => {
  317.     const keyUpdate = originalObject[key];
  318.     // determine if that was an op
  319.     if (keyUpdate && typeof keyUpdate === 'object' && keyUpdate.__op
  320.       && ['Add', 'AddUnique', 'Remove', 'Increment'].indexOf(keyUpdate.__op) > -1) {
  321.       // only valid ops that produce an actionable result
  322.       response[key] = result[key];
  323.     }
  324.   });
  325.   return Promise.resolve(response);
  326. }
  327.  
  328. // Collect all relation-updating operations from a REST-format update.
  329. // Returns a list of all relation updates to perform
  330. // This mutates update.
  331. DatabaseController.prototype.collectRelationUpdates = function(className, objectId, update) {
  332.   var ops = [];
  333.   var deleteMe = [];
  334.   objectId = update.objectId || objectId;
  335.  
  336.   var process = (op, key) => {
  337.     if (!op) {
  338.       return;
  339.     }
  340.     if (op.__op == 'AddRelation') {
  341.       ops.push({key, op});
  342.       deleteMe.push(key);
  343.     }
  344.  
  345.     if (op.__op == 'RemoveRelation') {
  346.       ops.push({key, op});
  347.       deleteMe.push(key);
  348.     }
  349.  
  350.     if (op.__op == 'Batch') {
  351.       for (var x of op.ops) {
  352.         process(x, key);
  353.       }
  354.     }
  355.   };
  356.  
  357.   for (const key in update) {
  358.     process(update[key], key);
  359.   }
  360.   for (const key of deleteMe) {
  361.     delete update[key];
  362.   }
  363.   return ops;
  364. }
  365.  
  366. // Processes relation-updating operations from a REST-format update.
  367. // Returns a promise that resolves when all updates have been performed
  368. DatabaseController.prototype.handleRelationUpdates = function(className, objectId, update, ops) {
  369.   var pending = [];
  370.   objectId = update.objectId || objectId;
  371.   ops.forEach(({key, op}) => {
  372.     if (!op) {
  373.       return;
  374.     }
  375.     if (op.__op == 'AddRelation') {
  376.       for (const object of op.objects) {
  377.         pending.push(this.addRelation(key, className,
  378.                                       objectId,
  379.                                       object.objectId));
  380.       }
  381.     }
  382.  
  383.     if (op.__op == 'RemoveRelation') {
  384.       for (const object of op.objects) {
  385.         pending.push(this.removeRelation(key, className,
  386.                                          objectId,
  387.                                          object.objectId));
  388.       }
  389.     }
  390.   });
  391.  
  392.   return Promise.all(pending);
  393. };
  394.  
  395. // Adds a relation.
  396. // Returns a promise that resolves successfully iff the add was successful.
  397. const relationSchema = { fields: { relatedId: { type: 'String' }, owningId: { type: 'String' } } };
  398. DatabaseController.prototype.addRelation = function(key, fromClassName, fromId, toId) {
  399.   const doc = {
  400.     relatedId: toId,
  401.     owningId : fromId
  402.   };
  403.   return this.adapter.upsertOneObject(`_Join:${key}:${fromClassName}`, relationSchema, doc, doc);
  404. };
  405.  
  406. // Removes a relation.
  407. // Returns a promise that resolves successfully iff the remove was
  408. // successful.
  409. DatabaseController.prototype.removeRelation = function(key, fromClassName, fromId, toId) {
  410.   var doc = {
  411.     relatedId: toId,
  412.     owningId: fromId
  413.   };
  414.   return this.adapter.deleteObjectsByQuery(`_Join:${key}:${fromClassName}`, relationSchema, doc)
  415.   .catch(error => {
  416.     // We don't care if they try to delete a non-existent relation.
  417.     if (error.code == Parse.Error.OBJECT_NOT_FOUND) {
  418.       return;
  419.     }
  420.     throw error;
  421.   });
  422. };
  423.  
  424. // Removes objects matches this query from the database.
  425. // Returns a promise that resolves successfully iff the object was
  426. // deleted.
  427. // Options:
  428. //   acl:  a list of strings. If the object to be updated has an ACL,
  429. //         one of the provided strings must provide the caller with
  430. //         write permissions.
  431. DatabaseController.prototype.destroy = function(className, query, { acl } = {}) {
  432.   const isMaster = acl === undefined;
  433.   const aclGroup = acl || [];
  434.  
  435.   return this.loadSchema()
  436.   .then(schemaController => {
  437.     return (isMaster ? Promise.resolve() : schemaController.validatePermission(className, aclGroup, 'delete'))
  438.     .then(() => {
  439.       if (!isMaster) {
  440.         query = this.addPointerPermissions(schemaController, className, 'delete', query, aclGroup);
  441.         if (!query) {
  442.           throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.');
  443.         }
  444.       }
  445.       // delete by query
  446.       if (acl) {
  447.         query = addWriteACL(query, acl);
  448.       }
  449.       validateQuery(query);
  450.       return schemaController.getOneSchema(className)
  451.       .catch(error => {
  452.         // If the schema doesn't exist, pretend it exists with no fields. This behaviour
  453.         // will likely need revisiting.
  454.         if (error === undefined) {
  455.           return { fields: {} };
  456.         }
  457.         throw error;
  458.       })
  459.       .then(parseFormatSchema => this.adapter.deleteObjectsByQuery(className, parseFormatSchema, query))
  460.       .catch(error => {
  461.         // When deleting sessions while changing passwords, don't throw an error if they don't have any sessions.
  462.         if (className === "_Session" && error.code === Parse.Error.OBJECT_NOT_FOUND) {
  463.           return Promise.resolve({});
  464.         }
  465.         throw error;
  466.       });
  467.     });
  468.   });
  469. };
  470.  
  471. const flattenUpdateOperatorsForCreate = object => {
  472.   for (const key in object) {
  473.     if (object[key] && object[key].__op) {
  474.       switch (object[key].__op) {
  475.       case 'Increment':
  476.         if (typeof object[key].amount !== 'number') {
  477.           throw new Parse.Error(Parse.Error.INVALID_JSON, 'objects to add must be an array');
  478.         }
  479.         object[key] = object[key].amount;
  480.         break;
  481.       case 'Add':
  482.         if (!(object[key].objects instanceof Array)) {
  483.           throw new Parse.Error(Parse.Error.INVALID_JSON, 'objects to add must be an array');
  484.         }
  485.         object[key] = object[key].objects;
  486.         break;
  487.       case 'AddUnique':
  488.         if (!(object[key].objects instanceof Array)) {
  489.           throw new Parse.Error(Parse.Error.INVALID_JSON, 'objects to add must be an array');
  490.         }
  491.         object[key] = object[key].objects;
  492.         break;
  493.       case 'Remove':
  494.         if (!(object[key].objects instanceof Array)) {
  495.           throw new Parse.Error(Parse.Error.INVALID_JSON, 'objects to add must be an array');
  496.         }
  497.         object[key] = []
  498.         break;
  499.       case 'Delete':
  500.         delete object[key];
  501.         break;
  502.       default:
  503.         throw new Parse.Error(Parse.Error.COMMAND_UNAVAILABLE, `The ${object[key].__op} operator is not supported yet.`);
  504.       }
  505.     }
  506.   }
  507. }
  508.  
  509. const transformAuthData = (className, object, schema) => {
  510.   if (object.authData && className === '_User') {
  511.     Object.keys(object.authData).forEach(provider => {
  512.       const providerData = object.authData[provider];
  513.       const fieldName = `_auth_data_${provider}`;
  514.       if (providerData == null) {
  515.         object[fieldName] = {
  516.           __op: 'Delete'
  517.         }
  518.       } else {
  519.         object[fieldName] = providerData;
  520.         schema.fields[fieldName] = { type: 'Object' }
  521.       }
  522.     });
  523.     delete object.authData;
  524.   }
  525. }
  526.  
  527. // Inserts an object into the database.
  528. // Returns a promise that resolves successfully iff the object saved.
  529. DatabaseController.prototype.create = function(className, object, { acl } = {}) {
  530.   // Make a copy of the object, so we don't mutate the incoming data.
  531.   const originalObject = object;
  532.   object = transformObjectACL(object);
  533.  
  534.   object.createdAt = { iso: object.createdAt, __type: 'Date' };
  535.   object.updatedAt = { iso: object.updatedAt, __type: 'Date' };
  536.  
  537.   var isMaster = acl === undefined;
  538.   var aclGroup = acl || [];
  539.   const relationUpdates = this.collectRelationUpdates(className, null, object);
  540.   return this.validateClassName(className)
  541.   .then(() => this.loadSchema())
  542.   .then(schemaController => {
  543.     return (isMaster ? Promise.resolve() : schemaController.validatePermission(className, aclGroup, 'create'))
  544.     .then(() => schemaController.enforceClassExists(className))
  545.     .then(() => schemaController.reloadData())
  546.     .then(() => schemaController.getOneSchema(className, true))
  547.     .then(schema => {
  548.       transformAuthData(className, object, schema);
  549.       flattenUpdateOperatorsForCreate(object);
  550.       return this.adapter.createObject(className, SchemaController.convertSchemaToAdapterSchema(schema), object);
  551.     })
  552.     .then(result => {
  553.       return this.handleRelationUpdates(className, null, object, relationUpdates).then(() => {
  554.         return sanitizeDatabaseResult(originalObject, result.ops[0])
  555.       });
  556.     });
  557.   })
  558. };
  559.  
  560. DatabaseController.prototype.canAddField = function(schema, className, object, aclGroup) {
  561.   const classSchema = schema.data[className];
  562.   if (!classSchema) {
  563.     return Promise.resolve();
  564.   }
  565.   const fields = Object.keys(object);
  566.   const schemaFields = Object.keys(classSchema);
  567.   const newKeys = fields.filter((field) => {
  568.     return schemaFields.indexOf(field) < 0;
  569.   })
  570.   if (newKeys.length > 0) {
  571.     return schema.validatePermission(className, aclGroup, 'addField');
  572.   }
  573.   return Promise.resolve();
  574. }
  575.  
  576. // Won't delete collections in the system namespace
  577. // Returns a promise.
  578. DatabaseController.prototype.deleteEverything = function() {
  579.   this.schemaPromise = null;
  580.   return Promise.all([
  581.     this.adapter.deleteAllClasses(),
  582.     this.schemaCache.clear()
  583.   ]);
  584. };
  585.  
  586. // Returns a promise for a list of related ids given an owning id.
  587. // className here is the owning className.
  588. DatabaseController.prototype.relatedIds = function(className, key, owningId) {
  589.   return this.adapter.find(joinTableName(className, key), relationSchema, { owningId }, {})
  590.   .then(results => results.map(result => result.relatedId));
  591. };
  592.  
  593. // Returns a promise for a list of owning ids given some related ids.
  594. // className here is the owning className.
  595. DatabaseController.prototype.owningIds = function(className, key, relatedIds) {
  596.   return this.adapter.find(joinTableName(className, key), relationSchema, { relatedId: { '$in': relatedIds } }, {})
  597.   .then(results => results.map(result => result.owningId));
  598. };
  599.  
  600. // Modifies query so that it no longer has $in on relation fields, or
  601. // equal-to-pointer constraints on relation fields.
  602. // Returns a promise that resolves when query is mutated
  603. DatabaseController.prototype.reduceInRelation = function(className, query, schema) {
  604.  
  605.   // Search for an in-relation or equal-to-relation
  606.   // Make it sequential for now, not sure of paralleization side effects
  607.   if (query['$or']) {
  608.     const ors = query['$or'];
  609.     return Promise.all(ors.map((aQuery, index) => {
  610.       return this.reduceInRelation(className, aQuery, schema).then((aQuery) => {
  611.         query['$or'][index] = aQuery;
  612.       });
  613.     })).then(() => {
  614.       return Promise.resolve(query);
  615.     });
  616.   }
  617.  
  618.   const promises = Object.keys(query).map((key) => {
  619.     if (query[key] && (query[key]['$in'] || query[key]['$ne'] || query[key]['$nin'] || query[key].__type == 'Pointer')) {
  620.       const t = schema.getExpectedType(className, key);
  621.       if (!t || t.type !== 'Relation') {
  622.         return Promise.resolve(query);
  623.       }
  624.       // Build the list of queries
  625.       const queries = Object.keys(query[key]).map((constraintKey) => {
  626.         let relatedIds;
  627.         let isNegation = false;
  628.         if (constraintKey === 'objectId') {
  629.           relatedIds = [query[key].objectId];
  630.         } else if (constraintKey == '$in') {
  631.           relatedIds = query[key]['$in'].map(r => r.objectId);
  632.         } else if (constraintKey == '$nin') {
  633.           isNegation = true;
  634.           relatedIds = query[key]['$nin'].map(r => r.objectId);
  635.         } else if (constraintKey == '$ne') {
  636.           isNegation = true;
  637.           relatedIds = [query[key]['$ne'].objectId];
  638.         } else {
  639.           return;
  640.         }
  641.         return {
  642.           isNegation,
  643.           relatedIds
  644.         }
  645.       });
  646.  
  647.       // remove the current queryKey as we don,t need it anymore
  648.       delete query[key];
  649.       // execute each query independnently to build the list of
  650.       // $in / $nin
  651.       const promises = queries.map((q) => {
  652.         if (!q) {
  653.           return Promise.resolve();
  654.         }
  655.         return this.owningIds(className, key, q.relatedIds).then((ids) => {
  656.           if (q.isNegation) {
  657.             this.addNotInObjectIdsIds(ids, query);
  658.           } else {
  659.             this.addInObjectIdsIds(ids, query);
  660.           }
  661.           return Promise.resolve();
  662.         });
  663.       });
  664.  
  665.       return Promise.all(promises).then(() => {
  666.         return Promise.resolve();
  667.       })
  668.  
  669.     }
  670.     return Promise.resolve();
  671.   })
  672.  
  673.   return Promise.all(promises).then(() => {
  674.     return Promise.resolve(query);
  675.   })
  676. };
  677.  
  678. // Modifies query so that it no longer has $relatedTo
  679. // Returns a promise that resolves when query is mutated
  680. DatabaseController.prototype.reduceRelationKeys = function(className, query) {
  681.  
  682.   if (query['$or']) {
  683.     return Promise.all(query['$or'].map((aQuery) => {
  684.       return this.reduceRelationKeys(className, aQuery);
  685.     }));
  686.   }
  687.  
  688.   var relatedTo = query['$relatedTo'];
  689.   if (relatedTo) {
  690.     return this.relatedIds(
  691.       relatedTo.object.className,
  692.       relatedTo.key,
  693.       relatedTo.object.objectId).then((ids) => {
  694.         delete query['$relatedTo'];
  695.         this.addInObjectIdsIds(ids, query);
  696.         return this.reduceRelationKeys(className, query);
  697.       });
  698.   }
  699. };
  700.  
  701. DatabaseController.prototype.addInObjectIdsIds = function(ids = null, query) {
  702.   const idsFromString = typeof query.objectId === 'string' ? [query.objectId] : null;
  703.   const idsFromEq = query.objectId && query.objectId['$eq'] ? [query.objectId['$eq']] : null;
  704.   const idsFromIn = query.objectId && query.objectId['$in'] ? query.objectId['$in'] : null;
  705.  
  706.   const allIds = [idsFromString, idsFromEq, idsFromIn, ids].filter(list => list !== null);
  707.   const totalLength = allIds.reduce((memo, list) => memo + list.length, 0);
  708.  
  709.   let idsIntersection = [];
  710.   if (totalLength > 125) {
  711.     idsIntersection = intersect.big(allIds);
  712.   } else {
  713.     idsIntersection = intersect(allIds);
  714.   }
  715.  
  716.   // Need to make sure we don't clobber existing shorthand $eq constraints on objectId.
  717.   if (!('objectId' in query)) {
  718.     query.objectId = {};
  719.   } else if (typeof query.objectId === 'string') {
  720.     query.objectId = {
  721.       $eq: query.objectId
  722.     };
  723.   }
  724.   query.objectId['$in'] = idsIntersection;
  725.  
  726.   return query;
  727. }
  728.  
  729. DatabaseController.prototype.addNotInObjectIdsIds = function(ids = [], query) {
  730.   const idsFromNin = query.objectId && query.objectId['$nin'] ? query.objectId['$nin'] : [];
  731.   let allIds = [...idsFromNin,...ids].filter(list => list !== null);
  732.  
  733.   // make a set and spread to remove duplicates
  734.   allIds = [...new Set(allIds)];
  735.  
  736.   // Need to make sure we don't clobber existing shorthand $eq constraints on objectId.
  737.   if (!('objectId' in query)) {
  738.     query.objectId = {};
  739.   } else if (typeof query.objectId === 'string') {
  740.     query.objectId = {
  741.       $eq: query.objectId
  742.     };
  743.   }
  744.  
  745.   query.objectId['$nin'] = allIds;
  746.   return query;
  747. }
  748.  
  749. // Runs a query on the database.
  750. // Returns a promise that resolves to a list of items.
  751. // Options:
  752. //   skip    number of results to skip.
  753. //   limit   limit to this number of results.
  754. //   sort    an object where keys are the fields to sort by.
  755. //           the value is +1 for ascending, -1 for descending.
  756. //   count   run a count instead of returning results.
  757. //   acl     restrict this operation with an ACL for the provided array
  758. //           of user objectIds and roles. acl: null means no user.
  759. //           when this field is not present, don't do anything regarding ACLs.
  760. // TODO: make userIds not needed here. The db adapter shouldn't know
  761. // anything about users, ideally. Then, improve the format of the ACL
  762. // arg to work like the others.
  763. DatabaseController.prototype.find = function(className, query, {
  764.   skip,
  765.   limit,
  766.   acl,
  767.   sort = {},
  768.   count,
  769.   keys,
  770.   op
  771. } = {}) {
  772.   const isMaster = acl === undefined;
  773.   const aclGroup = acl || [];
  774.   op = op || (typeof query.objectId == 'string' && Object.keys(query).length === 1 ? 'get' : 'find');
  775.   // Count operation if counting
  776.   op = (count === true ? 'count' : op);
  777.  
  778.   let classExists = true;
  779.   return this.loadSchema()
  780.   .then(schemaController => {
  781.     //Allow volatile classes if querying with Master (for _PushStatus)
  782.     //TODO: Move volatile classes concept into mongo adatper, postgres adapter shouldn't care
  783.     //that api.parse.com breaks when _PushStatus exists in mongo.
  784.     return schemaController.getOneSchema(className, isMaster)
  785.     .catch(error => {
  786.       // Behaviour for non-existent classes is kinda weird on Parse.com. Probably doesn't matter too much.
  787.       // For now, pretend the class exists but has no objects,
  788.       if (error === undefined) {
  789.         classExists = false;
  790.         return { fields: {} };
  791.       }
  792.       throw error;
  793.     })
  794.     .then(schema => {
  795.       // Parse.com treats queries on _created_at and _updated_at as if they were queries on createdAt and updatedAt,
  796.       // so duplicate that behaviour here. If both are specified, the corrent behaviour to match Parse.com is to
  797.       // use the one that appears first in the sort list.
  798.       if (sort._created_at) {
  799.         sort.createdAt = sort._created_at;
  800.         delete sort._created_at;
  801.       }
  802.       if (sort._updated_at) {
  803.         sort.updatedAt = sort._updated_at;
  804.         delete sort._updated_at;
  805.       }
  806.       Object.keys(sort).forEach(fieldName => {
  807.         if (fieldName.match(/^authData\.([a-zA-Z0-9_]+)\.id$/)) {
  808.           throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, `Cannot sort by ${fieldName}`);
  809.         }
  810.         if (!SchemaController.fieldNameIsValid(fieldName)) {
  811.           throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, `Invalid field name: ${fieldName}.`);
  812.         }
  813.       });
  814.       return (isMaster ? Promise.resolve() : schemaController.validatePermission(className, aclGroup, op))
  815.       .then(() => this.reduceRelationKeys(className, query))
  816.       .then(() => this.reduceInRelation(className, query, schemaController))
  817.       .then(() => {
  818.         if (!isMaster) {
  819.           query = this.addPointerPermissions(schemaController, className, op, query, aclGroup);
  820.         }
  821.         if (!query) {
  822.           if (op == 'get') {
  823.             throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.');
  824.           } else {
  825.             return [];
  826.           }
  827.         }
  828.         if (!isMaster) {
  829.           query = addReadACL(query, aclGroup);
  830.         }
  831.         validateQuery(query);
  832.         if (count) {
  833.           if (!classExists) {
  834.             return 0;
  835.           } else {
  836.             return this.adapter.count(className, schema, query);
  837.           }
  838.         } else {
  839.           if (!classExists) {
  840.             return [];
  841.           } else {
  842.             return this.adapter.find(className, schema, query, { skip, limit, sort, keys })
  843.             .then(objects => objects.map(object => {
  844.               object = untransformObjectACL(object);
  845.               return filterSensitiveData(isMaster, aclGroup, className, object)
  846.             }));
  847.           }
  848.         }
  849.       });
  850.     });
  851.   });
  852. };
  853.  
  854. // Transforms a Database format ACL to a REST API format ACL
  855. const untransformObjectACL = ({_rperm, _wperm, ...output}) => {
  856.   if (_rperm || _wperm) {
  857.     output.ACL = {};
  858.  
  859.     (_rperm || []).forEach(entry => {
  860.       if (!output.ACL[entry]) {
  861.         output.ACL[entry] = { read: true };
  862.       } else {
  863.         output.ACL[entry]['read'] = true;
  864.       }
  865.     });
  866.  
  867.     (_wperm || []).forEach(entry => {
  868.       if (!output.ACL[entry]) {
  869.         output.ACL[entry] = { write: true };
  870.       } else {
  871.         output.ACL[entry]['write'] = true;
  872.       }
  873.     });
  874.   }
  875.   return output;
  876. }
  877.  
  878. DatabaseController.prototype.deleteSchema = function(className) {
  879.   return this.loadSchema(true)
  880.   .then(schemaController => schemaController.getOneSchema(className, true))
  881.   .catch(error => {
  882.     if (error === undefined) {
  883.       return { fields: {} };
  884.     } else {
  885.       throw error;
  886.     }
  887.   })
  888.   .then(schema => {
  889.     return this.collectionExists(className)
  890.     .then(() => this.adapter.count(className, { fields: {} }))
  891.     .then(count => {
  892.       if (count > 0) {
  893.         throw new Parse.Error(255, `Class ${className} is not empty, contains ${count} objects, cannot drop schema.`);
  894.       }
  895.       return this.adapter.deleteClass(className);
  896.     })
  897.     .then(wasParseCollection => {
  898.       if (wasParseCollection) {
  899.         const relationFieldNames = Object.keys(schema.fields).filter(fieldName => schema.fields[fieldName].type === 'Relation');
  900.         return Promise.all(relationFieldNames.map(name => this.adapter.deleteClass(joinTableName(className, name))));
  901.       } else {
  902.         return Promise.resolve();
  903.       }
  904.     });
  905.   })
  906. }
  907.  
  908. DatabaseController.prototype.addPointerPermissions = function(schema, className, operation, query, aclGroup = []) {
  909.   // Check if class has public permission for operation
  910.   // If the BaseCLP pass, let go through
  911.   if (schema.testBaseCLP(className, aclGroup, operation)) {
  912.     return query;
  913.   }
  914.   const perms = schema.perms[className];
  915.   const field = ['get', 'find'].indexOf(operation) > -1 ? 'readUserFields' : 'writeUserFields';
  916.   const userACL = aclGroup.filter((acl) => {
  917.     return acl.indexOf('role:') != 0 && acl != '*';
  918.   });
  919.   // the ACL should have exactly 1 user
  920.   if (perms && perms[field] && perms[field].length > 0) {
  921.     // No user set return undefined
  922.     // If the length is > 1, that means we didn't dedup users correctly
  923.     if (userACL.length != 1) {
  924.       return;
  925.     }
  926.     const userId = userACL[0];
  927.     const userPointer =  {
  928.       "__type": "Pointer",
  929.       "className": "_User",
  930.       "objectId": userId
  931.     };
  932.  
  933.     const permFields = perms[field];
  934.     const ors = permFields.map((key) => {
  935.       const q = {
  936.         [key]: userPointer
  937.       };
  938.       return {'$and': [q, query]};
  939.     });
  940.     if (ors.length > 1) {
  941.       return {'$or': ors};
  942.     }
  943.     return ors[0];
  944.   } else {
  945.     return query;
  946.   }
  947. }
  948.  
  949. // TODO: create indexes on first creation of a _User object. Otherwise it's impossible to
  950. // have a Parse app without it having a _User collection.
  951. DatabaseController.prototype.performInitialization = function() {
  952.   const requiredUserFields = { fields: { ...SchemaController.defaultColumns._Default, ...SchemaController.defaultColumns._User } };
  953.   const requiredRoleFields = { fields: { ...SchemaController.defaultColumns._Default, ...SchemaController.defaultColumns._Role } };
  954.  
  955.   const userClassPromise = this.loadSchema()
  956.     .then(schema => schema.enforceClassExists('_User'))
  957.   const roleClassPromise = this.loadSchema()
  958.     .then(schema => schema.enforceClassExists('_Role'))
  959.  
  960.   const usernameUniqueness = userClassPromise
  961.     .then(() => this.adapter.ensureUniqueness('_User', requiredUserFields, ['username']))
  962.     .catch(error => {
  963.       logger.warn('Unable to ensure uniqueness for usernames: ', error);
  964.       throw error;
  965.     });
  966.  
  967.   const emailUniqueness = userClassPromise
  968.     .then(() => this.adapter.ensureUniqueness('_User', requiredUserFields, ['email']))
  969.     .catch(error => {
  970.       logger.warn('Unable to ensure uniqueness for user email addresses: ', error);
  971.       throw error;
  972.     });
  973.  
  974.   const roleUniqueness = roleClassPromise
  975.     .then(() => this.adapter.ensureUniqueness('_Role', requiredRoleFields, ['name']))
  976.     .catch(error => {
  977.       logger.warn('Unable to ensure uniqueness for role name: ', error);
  978.       throw error;
  979.     });
  980.  
  981.   // Create tables for volatile classes
  982.   const adapterInit = this.adapter.performInitialization({ VolatileClassesSchemas: SchemaController.VolatileClassesSchemas });
  983.   return Promise.all([usernameUniqueness, emailUniqueness, roleUniqueness, adapterInit]);
  984. }
  985.  
  986. function joinTableName(className, key) {
  987.   return `_Join:${key}:${className}`;
  988. }
  989.  
  990. // Expose validateQuery for tests
  991. DatabaseController._validateQuery = validateQuery;
  992. module.exports = DatabaseController;
  993.  
downloadDatabaseController.js Source code - Download parse-server Source code
Related Source Codes/Software:
react-boilerplate - 2017-06-07
webtorrent - Streaming torrent client for the web ... 2017-06-06
machine-learning-for-software-engineers - A complete daily plan for studying to become a mac... 2017-06-06
upterm - A terminal emulator for the 21st century. 2017-06-06
lottie-android - Render After Effects animations natively on Androi... 2017-06-07
AsyncDisplayKit - Smooth asynchronous user interfaces for iOS apps. ... 2017-06-07
ionicons - The premium icon font for Ionic ... 2017-06-07
storybook - 2017-06-07
prettier - Prettier is an opinionated JavaScript formatter. ... 2017-06-08
CRYENGINE - CRYENGINE is a powerful real-time game development... 2017-06-11
postal - 2017-06-11
reactide - Reactide is the first dedicated IDE for React web ... 2017-06-11
rkt - rkt is a pod-native container engine for Linux. It... 2017-06-11
uWebSockets - Tiny WebSockets https://for... 2017-06-11
realworld - TodoMVC for the RealWorld - Exemplary fullstack Me... 2017-06-11
goreplay - GoReplay is an open-source tool for capturing and ... 2017-06-10
pyenv - Simple Python version management 2017-06-10
redux-saga - An alternative side effect model for Redux apps ... 2017-06-10
angular-starter - 2017-06-10

 Back to top