2016-04-14 19:24:56 -04:00
|
|
|
|
import log from '../../../logger';
|
2016-04-08 16:06:52 -07:00
|
|
|
|
import _ from 'lodash';
|
2016-01-28 10:58:12 -08:00
|
|
|
|
var mongodb = require('mongodb');
|
|
|
|
|
|
var Parse = require('parse/node').Parse;
|
|
|
|
|
|
|
|
|
|
|
|
// Transforms a key-value pair from REST API form to Mongo form.
|
|
|
|
|
|
// This is the main entry point for converting anything from REST form
|
|
|
|
|
|
// to Mongo form; no conversion should happen that doesn't pass
|
|
|
|
|
|
// through this function.
|
|
|
|
|
|
// Schema should already be loaded.
|
|
|
|
|
|
//
|
|
|
|
|
|
// There are several options that can help transform:
|
|
|
|
|
|
//
|
|
|
|
|
|
// update: true indicates that __op operators like Add and Delete
|
|
|
|
|
|
// in the value are converted to a mongo update form. Otherwise they are
|
|
|
|
|
|
// converted to static data.
|
|
|
|
|
|
//
|
|
|
|
|
|
// validate: true indicates that key names are to be validated.
|
|
|
|
|
|
//
|
|
|
|
|
|
// Returns an object with {key: key, value: value}.
|
2016-04-25 18:22:20 -07:00
|
|
|
|
function transformKeyValue(schema, className, restKey, restValue, {
|
2016-04-20 13:35:48 -07:00
|
|
|
|
inArray,
|
|
|
|
|
|
inObject,
|
|
|
|
|
|
update,
|
|
|
|
|
|
validate,
|
|
|
|
|
|
} = {}) {
|
2016-01-28 10:58:12 -08:00
|
|
|
|
// Check if the schema is known since it's a built-in field.
|
|
|
|
|
|
var key = restKey;
|
|
|
|
|
|
var timeField = false;
|
|
|
|
|
|
switch(key) {
|
|
|
|
|
|
case 'objectId':
|
|
|
|
|
|
case '_id':
|
|
|
|
|
|
key = '_id';
|
|
|
|
|
|
break;
|
|
|
|
|
|
case 'createdAt':
|
|
|
|
|
|
case '_created_at':
|
|
|
|
|
|
key = '_created_at';
|
|
|
|
|
|
timeField = true;
|
|
|
|
|
|
break;
|
|
|
|
|
|
case 'updatedAt':
|
|
|
|
|
|
case '_updated_at':
|
|
|
|
|
|
key = '_updated_at';
|
|
|
|
|
|
timeField = true;
|
|
|
|
|
|
break;
|
2016-02-16 23:43:09 -08:00
|
|
|
|
case '_email_verify_token':
|
|
|
|
|
|
key = "_email_verify_token";
|
|
|
|
|
|
break;
|
2016-02-27 14:46:29 -05:00
|
|
|
|
case '_perishable_token':
|
|
|
|
|
|
key = "_perishable_token";
|
|
|
|
|
|
break;
|
2016-01-28 10:58:12 -08:00
|
|
|
|
case 'sessionToken':
|
|
|
|
|
|
case '_session_token':
|
|
|
|
|
|
key = '_session_token';
|
|
|
|
|
|
break;
|
2016-01-30 15:50:54 -08:00
|
|
|
|
case 'expiresAt':
|
|
|
|
|
|
case '_expiresAt':
|
2016-02-01 22:20:17 -05:00
|
|
|
|
key = 'expiresAt';
|
2016-01-30 15:50:54 -08:00
|
|
|
|
timeField = true;
|
|
|
|
|
|
break;
|
2016-01-28 10:58:12 -08:00
|
|
|
|
case '_rperm':
|
|
|
|
|
|
case '_wperm':
|
|
|
|
|
|
return {key: key, value: restValue};
|
|
|
|
|
|
break;
|
|
|
|
|
|
case '$or':
|
2016-04-25 18:22:20 -07:00
|
|
|
|
throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, 'you can only use $or in queries');
|
|
|
|
|
|
case '$and':
|
|
|
|
|
|
throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, 'you can only use $and in queries');
|
|
|
|
|
|
default:
|
|
|
|
|
|
// Other auth data
|
|
|
|
|
|
var authDataMatch = key.match(/^authData\.([a-zA-Z0-9_]+)\.id$/);
|
|
|
|
|
|
if (authDataMatch) {
|
|
|
|
|
|
throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, 'can only query on ' + key);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (validate && !key.match(/^[a-zA-Z][a-zA-Z0-9_\.]*$/)) {
|
|
|
|
|
|
throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, 'invalid key name: ' + key);
|
2016-01-28 10:58:12 -08:00
|
|
|
|
}
|
2016-04-25 18:22:20 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Handle special schema key changes
|
|
|
|
|
|
// TODO: it seems like this is likely to have edge cases where
|
|
|
|
|
|
// pointer types are missed
|
|
|
|
|
|
var expected = undefined;
|
|
|
|
|
|
if (schema && schema.getExpectedType) {
|
|
|
|
|
|
expected = schema.getExpectedType(className, key);
|
|
|
|
|
|
}
|
|
|
|
|
|
if ((expected && expected.type == 'Pointer') ||
|
|
|
|
|
|
(!expected && restValue && restValue.__type == 'Pointer')) {
|
|
|
|
|
|
key = '_p_' + key;
|
|
|
|
|
|
}
|
|
|
|
|
|
var expectedTypeIsArray = (expected && expected.type === 'Array');
|
|
|
|
|
|
|
|
|
|
|
|
// Handle atomic values
|
|
|
|
|
|
var value = transformAtom(restValue, false, { inArray, inObject });
|
|
|
|
|
|
if (value !== CannotTransform) {
|
|
|
|
|
|
if (timeField && (typeof value === 'string')) {
|
|
|
|
|
|
value = new Date(value);
|
|
|
|
|
|
}
|
|
|
|
|
|
return {key: key, value: value};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ACLs are handled before this method is called
|
|
|
|
|
|
// If an ACL key still exists here, something is wrong.
|
|
|
|
|
|
if (key === 'ACL') {
|
|
|
|
|
|
throw 'There was a problem transforming an ACL.';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Handle arrays
|
|
|
|
|
|
if (restValue instanceof Array) {
|
|
|
|
|
|
value = restValue.map((restObj) => {
|
|
|
|
|
|
var out = transformKeyValue(schema, className, restKey, restObj, { inArray: true });
|
|
|
|
|
|
return out.value;
|
|
|
|
|
|
});
|
|
|
|
|
|
return {key: key, value: value};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Handle update operators
|
|
|
|
|
|
value = transformUpdateOperator(restValue, !update);
|
|
|
|
|
|
if (value !== CannotTransform) {
|
|
|
|
|
|
return {key: key, value: value};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Handle normal objects by recursing
|
|
|
|
|
|
value = {};
|
|
|
|
|
|
for (var subRestKey in restValue) {
|
|
|
|
|
|
var subRestValue = restValue[subRestKey];
|
|
|
|
|
|
var out = transformKeyValue(schema, className, subRestKey, subRestValue, { inObject: true });
|
|
|
|
|
|
// For recursed objects, keep the keys in rest format
|
|
|
|
|
|
value[subRestKey] = out.value;
|
|
|
|
|
|
}
|
|
|
|
|
|
return {key: key, value: value};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-04-25 20:42:19 -07:00
|
|
|
|
const valueAsDate = value => {
|
|
|
|
|
|
if (typeof value === 'string') {
|
|
|
|
|
|
return new Date(value);
|
|
|
|
|
|
} else if (value instanceof Date) {
|
|
|
|
|
|
return value;
|
|
|
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function transformQueryKeyValue(schema, className, key, value, { validate } = {}) {
|
2016-04-25 18:22:20 -07:00
|
|
|
|
// Check if the schema is known since it's a built-in field.
|
|
|
|
|
|
switch(key) {
|
|
|
|
|
|
case 'createdAt':
|
2016-04-25 20:42:19 -07:00
|
|
|
|
if (valueAsDate(value)) {
|
|
|
|
|
|
return {key: '_created_at', value: valueAsDate(value)}
|
|
|
|
|
|
}
|
2016-04-25 18:22:20 -07:00
|
|
|
|
key = '_created_at';
|
|
|
|
|
|
break;
|
|
|
|
|
|
case 'updatedAt':
|
2016-04-25 20:42:19 -07:00
|
|
|
|
if (valueAsDate(value)) {
|
|
|
|
|
|
return {key: '_updated_at', value: valueAsDate(value)}
|
|
|
|
|
|
}
|
2016-04-25 18:22:20 -07:00
|
|
|
|
key = '_updated_at';
|
|
|
|
|
|
break;
|
|
|
|
|
|
case 'expiresAt':
|
2016-04-25 20:42:19 -07:00
|
|
|
|
if (valueAsDate(value)) {
|
|
|
|
|
|
return {key: 'expiresAt', value: valueAsDate(value)}
|
|
|
|
|
|
}
|
2016-04-25 18:22:20 -07:00
|
|
|
|
break;
|
2016-04-25 20:42:19 -07:00
|
|
|
|
case 'objectId': return {key: '_id', value}
|
|
|
|
|
|
case 'sessionToken': return {key: '_session_token', value}
|
2016-04-25 18:22:20 -07:00
|
|
|
|
case '_rperm':
|
|
|
|
|
|
case '_wperm':
|
2016-04-25 20:42:19 -07:00
|
|
|
|
case '_perishable_token':
|
|
|
|
|
|
case '_email_verify_token': return {key, value}
|
2016-04-25 18:22:20 -07:00
|
|
|
|
case '$or':
|
2016-04-25 20:42:19 -07:00
|
|
|
|
if (!(value instanceof Array)) {
|
2016-04-25 18:22:20 -07:00
|
|
|
|
throw new Parse.Error(Parse.Error.INVALID_QUERY, 'bad $or format - use an array value');
|
2016-01-28 10:58:12 -08:00
|
|
|
|
}
|
2016-04-25 20:42:19 -07:00
|
|
|
|
var mongoSubqueries = value.map(subQuery => transformWhere(schema, className, subQuery));
|
2016-01-28 10:58:12 -08:00
|
|
|
|
return {key: '$or', value: mongoSubqueries};
|
|
|
|
|
|
case '$and':
|
2016-04-25 20:42:19 -07:00
|
|
|
|
if (!(value instanceof Array)) {
|
2016-04-25 18:22:20 -07:00
|
|
|
|
throw new Parse.Error(Parse.Error.INVALID_QUERY, 'bad $and format - use an array value');
|
2016-01-28 10:58:12 -08:00
|
|
|
|
}
|
2016-04-25 20:42:19 -07:00
|
|
|
|
var mongoSubqueries = value.map(subQuery => transformWhere(schema, className, subQuery));
|
2016-01-28 10:58:12 -08:00
|
|
|
|
return {key: '$and', value: mongoSubqueries};
|
2016-03-17 12:05:31 -04:00
|
|
|
|
default:
|
2016-02-04 14:03:39 -05:00
|
|
|
|
// Other auth data
|
|
|
|
|
|
var authDataMatch = key.match(/^authData\.([a-zA-Z0-9_]+)\.id$/);
|
2016-04-25 20:54:47 -07:00
|
|
|
|
if (authDataMatch) {
|
2016-04-25 18:22:20 -07:00
|
|
|
|
var provider = authDataMatch[1];
|
|
|
|
|
|
// Special-case auth data.
|
2016-04-25 20:42:19 -07:00
|
|
|
|
return {key: `_auth_data_${provider}.id`, value};
|
2016-04-25 18:22:20 -07:00
|
|
|
|
}
|
2016-04-20 13:35:48 -07:00
|
|
|
|
if (validate && !key.match(/^[a-zA-Z][a-zA-Z0-9_\.]*$/)) {
|
2016-04-25 18:22:20 -07:00
|
|
|
|
throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, 'invalid key name: ' + key);
|
2016-01-28 10:58:12 -08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Handle special schema key changes
|
|
|
|
|
|
// TODO: it seems like this is likely to have edge cases where
|
|
|
|
|
|
// pointer types are missed
|
|
|
|
|
|
var expected = undefined;
|
|
|
|
|
|
if (schema && schema.getExpectedType) {
|
|
|
|
|
|
expected = schema.getExpectedType(className, key);
|
|
|
|
|
|
}
|
2016-04-12 17:39:27 -04:00
|
|
|
|
if ((expected && expected.type == 'Pointer') ||
|
2016-04-25 20:42:19 -07:00
|
|
|
|
(!expected && value && value.__type == 'Pointer')) {
|
2016-01-28 10:58:12 -08:00
|
|
|
|
key = '_p_' + key;
|
|
|
|
|
|
}
|
2016-04-20 13:35:48 -07:00
|
|
|
|
var expectedTypeIsArray = (expected && expected.type === 'Array');
|
2016-01-28 10:58:12 -08:00
|
|
|
|
|
|
|
|
|
|
// Handle query constraints
|
2016-04-25 20:42:19 -07:00
|
|
|
|
if (transformConstraint(value, expectedTypeIsArray) !== CannotTransform) {
|
|
|
|
|
|
return {key, value: transformConstraint(value, expectedTypeIsArray)};
|
2016-01-28 10:58:12 -08:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-04-25 20:42:19 -07:00
|
|
|
|
if (expectedTypeIsArray && !(value instanceof Array)) {
|
|
|
|
|
|
return {key, value: { '$all' : [value] }};
|
2016-01-28 10:58:12 -08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Handle atomic values
|
2016-04-25 20:42:19 -07:00
|
|
|
|
if (transformAtom(value, false) !== CannotTransform) {
|
|
|
|
|
|
return {key, value: transformAtom(value, false)};
|
2016-04-25 21:13:34 -07:00
|
|
|
|
} else {
|
|
|
|
|
|
throw new Parse.Error(Parse.Error.INVALID_JSON, `You cannot use ${value} as a query parameter.`);
|
2016-01-28 10:58:12 -08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Main exposed method to help run queries.
|
|
|
|
|
|
// restWhere is the "where" clause in REST API form.
|
|
|
|
|
|
// Returns the mongo form of the query.
|
|
|
|
|
|
// Throws a Parse.Error if the input query is invalid.
|
2016-04-25 17:21:24 -07:00
|
|
|
|
function transformWhere(schema, className, restWhere, { validate = true } = {}) {
|
2016-03-30 19:35:54 -07:00
|
|
|
|
let mongoWhere = {};
|
2016-01-28 10:58:12 -08:00
|
|
|
|
if (restWhere['ACL']) {
|
2016-03-30 19:35:54 -07:00
|
|
|
|
throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Cannot query on ACL.');
|
2016-01-28 10:58:12 -08:00
|
|
|
|
}
|
2016-03-30 19:35:54 -07:00
|
|
|
|
for (let restKey in restWhere) {
|
2016-04-25 18:22:20 -07:00
|
|
|
|
let out = transformQueryKeyValue(schema, className, restKey, restWhere[restKey], { validate });
|
2016-01-28 10:58:12 -08:00
|
|
|
|
mongoWhere[out.key] = out.value;
|
|
|
|
|
|
}
|
|
|
|
|
|
return mongoWhere;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-04-20 13:35:48 -07:00
|
|
|
|
const parseObjectKeyValueToMongoObjectKeyValue = (
|
|
|
|
|
|
schema,
|
|
|
|
|
|
className,
|
|
|
|
|
|
restKey,
|
|
|
|
|
|
restValue,
|
|
|
|
|
|
parseFormatSchema
|
|
|
|
|
|
) => {
|
|
|
|
|
|
// Check if the schema is known since it's a built-in field.
|
|
|
|
|
|
let transformedValue;
|
|
|
|
|
|
let coercedToDate;
|
|
|
|
|
|
switch(restKey) {
|
|
|
|
|
|
case 'objectId': return {key: '_id', value: restValue};
|
|
|
|
|
|
case 'createdAt':
|
|
|
|
|
|
transformedValue = transformAtom(restValue, false);
|
|
|
|
|
|
coercedToDate = typeof transformedValue === 'string' ? new Date(transformedValue) : transformedValue
|
|
|
|
|
|
return {key: '_created_at', value: coercedToDate};
|
|
|
|
|
|
case 'updatedAt':
|
|
|
|
|
|
transformedValue = transformAtom(restValue, false);
|
|
|
|
|
|
coercedToDate = typeof transformedValue === 'string' ? new Date(transformedValue) : transformedValue
|
|
|
|
|
|
return {key: '_updated_at', value: coercedToDate};
|
|
|
|
|
|
case 'expiresAt':
|
|
|
|
|
|
transformedValue = transformAtom(restValue, false);
|
|
|
|
|
|
coercedToDate = typeof transformedValue === 'string' ? new Date(transformedValue) : transformedValue
|
|
|
|
|
|
return {key: 'expiresAt', value: coercedToDate};
|
|
|
|
|
|
case '_rperm':
|
|
|
|
|
|
case '_wperm':
|
|
|
|
|
|
case '_email_verify_token':
|
|
|
|
|
|
case '_hashed_password':
|
|
|
|
|
|
case '_perishable_token': return {key: restKey, value: restValue};
|
|
|
|
|
|
case 'sessionToken': return {key: '_session_token', value: restValue};
|
|
|
|
|
|
default:
|
|
|
|
|
|
// Auth data should have been transformed already
|
|
|
|
|
|
if (restKey.match(/^authData\.([a-zA-Z0-9_]+)\.id$/)) {
|
|
|
|
|
|
throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, 'can only query on ' + restKey);
|
|
|
|
|
|
}
|
|
|
|
|
|
// Trust that the auth data has been transformed and save it directly
|
|
|
|
|
|
if (restKey.match(/^_auth_data_[a-zA-Z0-9_]+$/)) {
|
|
|
|
|
|
return {key: restKey, value: restValue};
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
//skip straight to transformAtom for Bytes, they don't show up in the schema for some reason
|
|
|
|
|
|
if (restValue && restValue.__type !== 'Bytes') {
|
|
|
|
|
|
//Note: We may not know the type of a field here, as the user could be saving (null) to a field
|
|
|
|
|
|
//That never existed before, meaning we can't infer the type.
|
|
|
|
|
|
if (parseFormatSchema.fields[restKey] && parseFormatSchema.fields[restKey].type == 'Pointer' || restValue.__type == 'Pointer') {
|
|
|
|
|
|
restKey = '_p_' + restKey;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Handle atomic values
|
|
|
|
|
|
var value = transformAtom(restValue, false, { inArray: false, inObject: false });
|
|
|
|
|
|
if (value !== CannotTransform) {
|
|
|
|
|
|
return {key: restKey, value: value};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ACLs are handled before this method is called
|
|
|
|
|
|
// If an ACL key still exists here, something is wrong.
|
|
|
|
|
|
if (restKey === 'ACL') {
|
|
|
|
|
|
throw 'There was a problem transforming an ACL.';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Handle arrays
|
|
|
|
|
|
if (restValue instanceof Array) {
|
|
|
|
|
|
value = restValue.map((restObj) => {
|
|
|
|
|
|
var out = transformKeyValue(schema, className, restKey, restObj, { inArray: true });
|
|
|
|
|
|
return out.value;
|
|
|
|
|
|
});
|
|
|
|
|
|
return {key: restKey, value: value};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Handle update operators. TODO: handle within Parse Server. DB adapter shouldn't see update operators in creates.
|
|
|
|
|
|
value = transformUpdateOperator(restValue, true);
|
|
|
|
|
|
if (value !== CannotTransform) {
|
|
|
|
|
|
return {key: restKey, value: value};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Handle normal objects by recursing
|
|
|
|
|
|
value = {};
|
|
|
|
|
|
for (var subRestKey in restValue) {
|
|
|
|
|
|
var subRestValue = restValue[subRestKey];
|
|
|
|
|
|
var out = transformKeyValue(schema, className, subRestKey, subRestValue, { inObject: true });
|
|
|
|
|
|
// For recursed objects, keep the keys in rest format
|
|
|
|
|
|
value[subRestKey] = out.value;
|
|
|
|
|
|
}
|
|
|
|
|
|
return {key: restKey, value: value};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-01-28 10:58:12 -08:00
|
|
|
|
// Main exposed method to create new objects.
|
|
|
|
|
|
// restCreate is the "create" clause in REST API form.
|
2016-04-20 13:35:48 -07:00
|
|
|
|
function parseObjectToMongoObjectForCreate(schema, className, restCreate, parseFormatSchema) {
|
2016-03-11 14:50:33 -05:00
|
|
|
|
if (className == '_User') {
|
|
|
|
|
|
restCreate = transformAuthData(restCreate);
|
|
|
|
|
|
}
|
2016-01-28 10:58:12 -08:00
|
|
|
|
var mongoCreate = transformACL(restCreate);
|
2016-04-20 13:35:48 -07:00
|
|
|
|
for (let restKey in restCreate) {
|
|
|
|
|
|
let { key, value } = parseObjectKeyValueToMongoObjectKeyValue(
|
|
|
|
|
|
schema,
|
|
|
|
|
|
className,
|
|
|
|
|
|
restKey,
|
|
|
|
|
|
restCreate[restKey],
|
|
|
|
|
|
parseFormatSchema
|
|
|
|
|
|
);
|
|
|
|
|
|
if (value !== undefined) {
|
|
|
|
|
|
mongoCreate[key] = value;
|
2016-01-28 10:58:12 -08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return mongoCreate;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Main exposed method to help update old objects.
|
|
|
|
|
|
function transformUpdate(schema, className, restUpdate) {
|
|
|
|
|
|
if (!restUpdate) {
|
|
|
|
|
|
throw 'got empty restUpdate';
|
|
|
|
|
|
}
|
2016-03-11 14:50:33 -05:00
|
|
|
|
if (className == '_User') {
|
|
|
|
|
|
restUpdate = transformAuthData(restUpdate);
|
|
|
|
|
|
}
|
2016-03-17 12:05:31 -04:00
|
|
|
|
|
2016-01-28 10:58:12 -08:00
|
|
|
|
var mongoUpdate = {};
|
|
|
|
|
|
var acl = transformACL(restUpdate);
|
|
|
|
|
|
if (acl._rperm || acl._wperm) {
|
|
|
|
|
|
mongoUpdate['$set'] = {};
|
|
|
|
|
|
if (acl._rperm) {
|
|
|
|
|
|
mongoUpdate['$set']['_rperm'] = acl._rperm;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (acl._wperm) {
|
|
|
|
|
|
mongoUpdate['$set']['_wperm'] = acl._wperm;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for (var restKey in restUpdate) {
|
2016-04-22 14:05:21 -07:00
|
|
|
|
var out = transformKeyValue(schema, className, restKey, restUpdate[restKey], {update: true});
|
2016-01-28 10:58:12 -08:00
|
|
|
|
|
|
|
|
|
|
// If the output value is an object with any $ keys, it's an
|
|
|
|
|
|
// operator that needs to be lifted onto the top level update
|
|
|
|
|
|
// object.
|
|
|
|
|
|
if (typeof out.value === 'object' && out.value !== null &&
|
|
|
|
|
|
out.value.__op) {
|
|
|
|
|
|
mongoUpdate[out.value.__op] = mongoUpdate[out.value.__op] || {};
|
|
|
|
|
|
mongoUpdate[out.value.__op][out.key] = out.value.arg;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
mongoUpdate['$set'] = mongoUpdate['$set'] || {};
|
|
|
|
|
|
mongoUpdate['$set'][out.key] = out.value;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return mongoUpdate;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-03-11 14:50:33 -05:00
|
|
|
|
function transformAuthData(restObject) {
|
|
|
|
|
|
if (restObject.authData) {
|
|
|
|
|
|
Object.keys(restObject.authData).forEach((provider) => {
|
2016-03-17 12:05:31 -04:00
|
|
|
|
let providerData = restObject.authData[provider];
|
|
|
|
|
|
if (providerData == null) {
|
|
|
|
|
|
restObject[`_auth_data_${provider}`] = {
|
|
|
|
|
|
__op: 'Delete'
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
restObject[`_auth_data_${provider}`] = providerData;
|
|
|
|
|
|
}
|
2016-03-11 14:50:33 -05:00
|
|
|
|
});
|
|
|
|
|
|
delete restObject.authData;
|
|
|
|
|
|
}
|
|
|
|
|
|
return restObject;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-01-28 10:58:12 -08:00
|
|
|
|
// Transforms a REST API formatted ACL object to our two-field mongo format.
|
|
|
|
|
|
// This mutates the restObject passed in to remove the ACL key.
|
|
|
|
|
|
function transformACL(restObject) {
|
|
|
|
|
|
var output = {};
|
|
|
|
|
|
if (!restObject['ACL']) {
|
|
|
|
|
|
return output;
|
|
|
|
|
|
}
|
|
|
|
|
|
var acl = restObject['ACL'];
|
|
|
|
|
|
var rperm = [];
|
|
|
|
|
|
var wperm = [];
|
|
|
|
|
|
for (var entry in acl) {
|
|
|
|
|
|
if (acl[entry].read) {
|
|
|
|
|
|
rperm.push(entry);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (acl[entry].write) {
|
|
|
|
|
|
wperm.push(entry);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2016-02-22 17:33:11 -08:00
|
|
|
|
output._rperm = rperm;
|
|
|
|
|
|
output._wperm = wperm;
|
2016-01-28 10:58:12 -08:00
|
|
|
|
delete restObject.ACL;
|
|
|
|
|
|
return output;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Transforms a mongo format ACL to a REST API format ACL key
|
|
|
|
|
|
// This mutates the mongoObject passed in to remove the _rperm/_wperm keys
|
|
|
|
|
|
function untransformACL(mongoObject) {
|
|
|
|
|
|
var output = {};
|
|
|
|
|
|
if (!mongoObject['_rperm'] && !mongoObject['_wperm']) {
|
|
|
|
|
|
return output;
|
|
|
|
|
|
}
|
|
|
|
|
|
var acl = {};
|
|
|
|
|
|
var rperm = mongoObject['_rperm'] || [];
|
|
|
|
|
|
var wperm = mongoObject['_wperm'] || [];
|
|
|
|
|
|
rperm.map((entry) => {
|
|
|
|
|
|
if (!acl[entry]) {
|
|
|
|
|
|
acl[entry] = {read: true};
|
|
|
|
|
|
} else {
|
|
|
|
|
|
acl[entry]['read'] = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
wperm.map((entry) => {
|
|
|
|
|
|
if (!acl[entry]) {
|
|
|
|
|
|
acl[entry] = {write: true};
|
|
|
|
|
|
} else {
|
|
|
|
|
|
acl[entry]['write'] = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
output['ACL'] = acl;
|
|
|
|
|
|
delete mongoObject._rperm;
|
|
|
|
|
|
delete mongoObject._wperm;
|
|
|
|
|
|
return output;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Transforms a key used in the REST API format to its mongo format.
|
|
|
|
|
|
function transformKey(schema, className, key) {
|
|
|
|
|
|
return transformKeyValue(schema, className, key, null, {validate: true}).key;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// A sentinel value that helper transformations return when they
|
|
|
|
|
|
// cannot perform a transformation
|
|
|
|
|
|
function CannotTransform() {}
|
|
|
|
|
|
|
|
|
|
|
|
// Helper function to transform an atom from REST format to Mongo format.
|
|
|
|
|
|
// An atom is anything that can't contain other expressions. So it
|
|
|
|
|
|
// includes things where objects are used to represent other
|
|
|
|
|
|
// datatypes, like pointers and dates, but it does not include objects
|
|
|
|
|
|
// or arrays with generic stuff inside.
|
|
|
|
|
|
// If options.inArray is true, we'll leave it in REST format.
|
|
|
|
|
|
// If options.inObject is true, we'll leave files in REST format.
|
|
|
|
|
|
// Raises an error if this cannot possibly be valid REST format.
|
|
|
|
|
|
// Returns CannotTransform if it's just not an atom, or if force is
|
|
|
|
|
|
// true, throws an error.
|
2016-04-18 17:10:30 -07:00
|
|
|
|
function transformAtom(atom, force, {
|
|
|
|
|
|
inArray,
|
|
|
|
|
|
inObject,
|
|
|
|
|
|
} = {}) {
|
2016-01-28 10:58:12 -08:00
|
|
|
|
switch(typeof atom) {
|
|
|
|
|
|
case 'string':
|
|
|
|
|
|
case 'number':
|
|
|
|
|
|
case 'boolean':
|
|
|
|
|
|
return atom;
|
|
|
|
|
|
case 'undefined':
|
2016-02-03 00:02:54 -08:00
|
|
|
|
return atom;
|
2016-01-28 10:58:12 -08:00
|
|
|
|
case 'symbol':
|
|
|
|
|
|
case 'function':
|
2016-04-18 17:10:30 -07:00
|
|
|
|
throw new Parse.Error(Parse.Error.INVALID_JSON, `cannot transform value: ${atom}`);
|
2016-01-28 10:58:12 -08:00
|
|
|
|
case 'object':
|
|
|
|
|
|
if (atom instanceof Date) {
|
|
|
|
|
|
// Technically dates are not rest format, but, it seems pretty
|
|
|
|
|
|
// clear what they should be transformed to, so let's just do it.
|
|
|
|
|
|
return atom;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (atom === null) {
|
|
|
|
|
|
return atom;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// TODO: check validity harder for the __type-defined types
|
|
|
|
|
|
if (atom.__type == 'Pointer') {
|
|
|
|
|
|
if (!inArray && !inObject) {
|
2016-04-18 17:10:30 -07:00
|
|
|
|
return `${atom.className}$${atom.objectId}`;
|
2016-01-28 10:58:12 -08:00
|
|
|
|
}
|
|
|
|
|
|
return {
|
|
|
|
|
|
__type: 'Pointer',
|
|
|
|
|
|
className: atom.className,
|
|
|
|
|
|
objectId: atom.objectId
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
2016-02-03 21:30:21 -08:00
|
|
|
|
if (DateCoder.isValidJSON(atom)) {
|
|
|
|
|
|
return DateCoder.JSONToDatabase(atom);
|
2016-01-28 10:58:12 -08:00
|
|
|
|
}
|
2016-02-03 21:30:21 -08:00
|
|
|
|
if (BytesCoder.isValidJSON(atom)) {
|
|
|
|
|
|
return BytesCoder.JSONToDatabase(atom);
|
2016-01-28 10:58:12 -08:00
|
|
|
|
}
|
2016-02-03 21:30:21 -08:00
|
|
|
|
if (GeoPointCoder.isValidJSON(atom)) {
|
|
|
|
|
|
return (inArray || inObject ? atom : GeoPointCoder.JSONToDatabase(atom));
|
2016-01-28 10:58:12 -08:00
|
|
|
|
}
|
2016-02-03 21:30:21 -08:00
|
|
|
|
if (FileCoder.isValidJSON(atom)) {
|
|
|
|
|
|
return (inArray || inObject ? atom : FileCoder.JSONToDatabase(atom));
|
2016-01-28 10:58:12 -08:00
|
|
|
|
}
|
2016-03-20 15:16:26 -04:00
|
|
|
|
if (inArray || inObject) {
|
|
|
|
|
|
return atom;
|
|
|
|
|
|
}
|
2016-01-28 10:58:12 -08:00
|
|
|
|
|
|
|
|
|
|
if (force) {
|
2016-04-18 17:10:30 -07:00
|
|
|
|
throw new Parse.Error(Parse.Error.INVALID_JSON, `bad atom: ${atom}`);
|
2016-01-28 10:58:12 -08:00
|
|
|
|
}
|
|
|
|
|
|
return CannotTransform;
|
|
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
|
// I don't think typeof can ever let us get here
|
2016-04-18 17:10:30 -07:00
|
|
|
|
throw new Parse.Error(Parse.Error.INTERNAL_SERVER_ERROR, `really did not expect value: ${atom}`);
|
2016-01-28 10:58:12 -08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Transforms a query constraint from REST API format to Mongo format.
|
|
|
|
|
|
// A constraint is something with fields like $lt.
|
|
|
|
|
|
// If it is not a valid constraint but it could be a valid something
|
|
|
|
|
|
// else, return CannotTransform.
|
|
|
|
|
|
// inArray is whether this is an array field.
|
|
|
|
|
|
function transformConstraint(constraint, inArray) {
|
|
|
|
|
|
if (typeof constraint !== 'object' || !constraint) {
|
|
|
|
|
|
return CannotTransform;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// keys is the constraints in reverse alphabetical order.
|
|
|
|
|
|
// This is a hack so that:
|
|
|
|
|
|
// $regex is handled before $options
|
|
|
|
|
|
// $nearSphere is handled before $maxDistance
|
|
|
|
|
|
var keys = Object.keys(constraint).sort().reverse();
|
|
|
|
|
|
var answer = {};
|
|
|
|
|
|
for (var key of keys) {
|
|
|
|
|
|
switch(key) {
|
|
|
|
|
|
case '$lt':
|
|
|
|
|
|
case '$lte':
|
|
|
|
|
|
case '$gt':
|
|
|
|
|
|
case '$gte':
|
|
|
|
|
|
case '$exists':
|
|
|
|
|
|
case '$ne':
|
2016-03-07 08:26:35 -05:00
|
|
|
|
case '$eq':
|
2016-01-28 10:58:12 -08:00
|
|
|
|
answer[key] = transformAtom(constraint[key], true,
|
|
|
|
|
|
{inArray: inArray});
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case '$in':
|
|
|
|
|
|
case '$nin':
|
|
|
|
|
|
var arr = constraint[key];
|
|
|
|
|
|
if (!(arr instanceof Array)) {
|
|
|
|
|
|
throw new Parse.Error(Parse.Error.INVALID_JSON,
|
|
|
|
|
|
'bad ' + key + ' value');
|
|
|
|
|
|
}
|
|
|
|
|
|
answer[key] = arr.map((v) => {
|
2016-03-24 20:42:06 -04:00
|
|
|
|
return transformAtom(v, true, { inArray: inArray });
|
2016-01-28 10:58:12 -08:00
|
|
|
|
});
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case '$all':
|
|
|
|
|
|
var arr = constraint[key];
|
|
|
|
|
|
if (!(arr instanceof Array)) {
|
|
|
|
|
|
throw new Parse.Error(Parse.Error.INVALID_JSON,
|
|
|
|
|
|
'bad ' + key + ' value');
|
|
|
|
|
|
}
|
|
|
|
|
|
answer[key] = arr.map((v) => {
|
|
|
|
|
|
return transformAtom(v, true, { inArray: true });
|
|
|
|
|
|
});
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case '$regex':
|
|
|
|
|
|
var s = constraint[key];
|
|
|
|
|
|
if (typeof s !== 'string') {
|
|
|
|
|
|
throw new Parse.Error(Parse.Error.INVALID_JSON, 'bad regex: ' + s);
|
|
|
|
|
|
}
|
|
|
|
|
|
answer[key] = s;
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case '$options':
|
|
|
|
|
|
var options = constraint[key];
|
|
|
|
|
|
if (!answer['$regex'] || (typeof options !== 'string')
|
|
|
|
|
|
|| !options.match(/^[imxs]+$/)) {
|
|
|
|
|
|
throw new Parse.Error(Parse.Error.INVALID_QUERY,
|
|
|
|
|
|
'got a bad $options');
|
|
|
|
|
|
}
|
|
|
|
|
|
answer[key] = options;
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case '$nearSphere':
|
|
|
|
|
|
var point = constraint[key];
|
|
|
|
|
|
answer[key] = [point.longitude, point.latitude];
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case '$maxDistance':
|
|
|
|
|
|
answer[key] = constraint[key];
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
// The SDKs don't seem to use these but they are documented in the
|
|
|
|
|
|
// REST API docs.
|
|
|
|
|
|
case '$maxDistanceInRadians':
|
|
|
|
|
|
answer['$maxDistance'] = constraint[key];
|
|
|
|
|
|
break;
|
|
|
|
|
|
case '$maxDistanceInMiles':
|
|
|
|
|
|
answer['$maxDistance'] = constraint[key] / 3959;
|
|
|
|
|
|
break;
|
|
|
|
|
|
case '$maxDistanceInKilometers':
|
|
|
|
|
|
answer['$maxDistance'] = constraint[key] / 6371;
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case '$select':
|
|
|
|
|
|
case '$dontSelect':
|
|
|
|
|
|
throw new Parse.Error(
|
|
|
|
|
|
Parse.Error.COMMAND_UNAVAILABLE,
|
|
|
|
|
|
'the ' + key + ' constraint is not supported yet');
|
|
|
|
|
|
|
|
|
|
|
|
case '$within':
|
|
|
|
|
|
var box = constraint[key]['$box'];
|
|
|
|
|
|
if (!box || box.length != 2) {
|
|
|
|
|
|
throw new Parse.Error(
|
|
|
|
|
|
Parse.Error.INVALID_JSON,
|
|
|
|
|
|
'malformatted $within arg');
|
|
|
|
|
|
}
|
|
|
|
|
|
answer[key] = {
|
|
|
|
|
|
'$box': [
|
|
|
|
|
|
[box[0].longitude, box[0].latitude],
|
|
|
|
|
|
[box[1].longitude, box[1].latitude]
|
|
|
|
|
|
]
|
|
|
|
|
|
};
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
|
if (key.match(/^\$+/)) {
|
|
|
|
|
|
throw new Parse.Error(
|
|
|
|
|
|
Parse.Error.INVALID_JSON,
|
|
|
|
|
|
'bad constraint: ' + key);
|
|
|
|
|
|
}
|
|
|
|
|
|
return CannotTransform;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return answer;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Transforms an update operator from REST format to mongo format.
|
|
|
|
|
|
// To be transformed, the input should have an __op field.
|
|
|
|
|
|
// If flatten is true, this will flatten operators to their static
|
|
|
|
|
|
// data format. For example, an increment of 2 would simply become a
|
|
|
|
|
|
// 2.
|
|
|
|
|
|
// The output for a non-flattened operator is a hash with __op being
|
|
|
|
|
|
// the mongo op, and arg being the argument.
|
|
|
|
|
|
// The output for a flattened operator is just a value.
|
|
|
|
|
|
// Returns CannotTransform if this cannot transform it.
|
|
|
|
|
|
// Returns undefined if this should be a no-op.
|
|
|
|
|
|
function transformUpdateOperator(operator, flatten) {
|
|
|
|
|
|
if (typeof operator !== 'object' || !operator.__op) {
|
|
|
|
|
|
return CannotTransform;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
switch(operator.__op) {
|
|
|
|
|
|
case 'Delete':
|
|
|
|
|
|
if (flatten) {
|
|
|
|
|
|
return undefined;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
return {__op: '$unset', arg: ''};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
case 'Increment':
|
|
|
|
|
|
if (typeof operator.amount !== 'number') {
|
|
|
|
|
|
throw new Parse.Error(Parse.Error.INVALID_JSON,
|
|
|
|
|
|
'incrementing must provide a number');
|
|
|
|
|
|
}
|
|
|
|
|
|
if (flatten) {
|
|
|
|
|
|
return operator.amount;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
return {__op: '$inc', arg: operator.amount};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
case 'Add':
|
|
|
|
|
|
case 'AddUnique':
|
|
|
|
|
|
if (!(operator.objects instanceof Array)) {
|
|
|
|
|
|
throw new Parse.Error(Parse.Error.INVALID_JSON,
|
|
|
|
|
|
'objects to add must be an array');
|
|
|
|
|
|
}
|
|
|
|
|
|
var toAdd = operator.objects.map((obj) => {
|
|
|
|
|
|
return transformAtom(obj, true, { inArray: true });
|
|
|
|
|
|
});
|
|
|
|
|
|
if (flatten) {
|
|
|
|
|
|
return toAdd;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
var mongoOp = {
|
|
|
|
|
|
Add: '$push',
|
|
|
|
|
|
AddUnique: '$addToSet'
|
|
|
|
|
|
}[operator.__op];
|
|
|
|
|
|
return {__op: mongoOp, arg: {'$each': toAdd}};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
case 'Remove':
|
|
|
|
|
|
if (!(operator.objects instanceof Array)) {
|
|
|
|
|
|
throw new Parse.Error(Parse.Error.INVALID_JSON,
|
|
|
|
|
|
'objects to remove must be an array');
|
|
|
|
|
|
}
|
|
|
|
|
|
var toRemove = operator.objects.map((obj) => {
|
|
|
|
|
|
return transformAtom(obj, true, { inArray: true });
|
|
|
|
|
|
});
|
|
|
|
|
|
if (flatten) {
|
|
|
|
|
|
return [];
|
|
|
|
|
|
} else {
|
|
|
|
|
|
return {__op: '$pullAll', arg: toRemove};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
|
throw new Parse.Error(
|
|
|
|
|
|
Parse.Error.COMMAND_UNAVAILABLE,
|
|
|
|
|
|
'the ' + operator.__op + ' op is not supported yet');
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-04-08 16:06:52 -07:00
|
|
|
|
const specialKeysForUntransform = [
|
|
|
|
|
|
'_id',
|
|
|
|
|
|
'_hashed_password',
|
|
|
|
|
|
'_acl',
|
|
|
|
|
|
'_email_verify_token',
|
|
|
|
|
|
'_perishable_token',
|
|
|
|
|
|
'_tombstone',
|
|
|
|
|
|
'_session_token',
|
|
|
|
|
|
'updatedAt',
|
|
|
|
|
|
'_updated_at',
|
|
|
|
|
|
'createdAt',
|
|
|
|
|
|
'_created_at',
|
|
|
|
|
|
'expiresAt',
|
|
|
|
|
|
'_expiresAt',
|
|
|
|
|
|
];
|
2016-01-28 10:58:12 -08:00
|
|
|
|
|
|
|
|
|
|
// Converts from a mongo-format object to a REST-format object.
|
|
|
|
|
|
// Does not strip out anything based on a lack of authentication.
|
2016-02-20 09:45:09 -05:00
|
|
|
|
function untransformObject(schema, className, mongoObject, isNestedObject = false) {
|
2016-01-28 10:58:12 -08:00
|
|
|
|
switch(typeof mongoObject) {
|
|
|
|
|
|
case 'string':
|
|
|
|
|
|
case 'number':
|
|
|
|
|
|
case 'boolean':
|
|
|
|
|
|
return mongoObject;
|
|
|
|
|
|
case 'undefined':
|
|
|
|
|
|
case 'symbol':
|
|
|
|
|
|
case 'function':
|
|
|
|
|
|
throw 'bad value in untransformObject';
|
|
|
|
|
|
case 'object':
|
|
|
|
|
|
if (mongoObject === null) {
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (mongoObject instanceof Array) {
|
2016-04-08 16:06:52 -07:00
|
|
|
|
return mongoObject.map(arrayEntry => {
|
|
|
|
|
|
return untransformObject(schema, className, arrayEntry, true);
|
2016-01-28 10:58:12 -08:00
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (mongoObject instanceof Date) {
|
|
|
|
|
|
return Parse._encode(mongoObject);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-04-12 21:19:47 -04:00
|
|
|
|
if (mongoObject instanceof mongodb.Long) {
|
|
|
|
|
|
return mongoObject.toNumber();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (mongoObject instanceof mongodb.Double) {
|
|
|
|
|
|
return mongoObject.value;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-02-03 21:30:21 -08:00
|
|
|
|
if (BytesCoder.isValidDatabaseObject(mongoObject)) {
|
|
|
|
|
|
return BytesCoder.databaseToJSON(mongoObject);
|
2016-01-28 10:58:12 -08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var restObject = untransformACL(mongoObject);
|
|
|
|
|
|
for (var key in mongoObject) {
|
2016-04-08 16:06:52 -07:00
|
|
|
|
if (isNestedObject && _.includes(specialKeysForUntransform, key)) {
|
|
|
|
|
|
restObject[key] = untransformObject(schema, className, mongoObject[key], true);
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
2016-01-28 10:58:12 -08:00
|
|
|
|
switch(key) {
|
|
|
|
|
|
case '_id':
|
|
|
|
|
|
restObject['objectId'] = '' + mongoObject[key];
|
|
|
|
|
|
break;
|
|
|
|
|
|
case '_hashed_password':
|
|
|
|
|
|
restObject['password'] = mongoObject[key];
|
|
|
|
|
|
break;
|
|
|
|
|
|
case '_acl':
|
2016-01-29 10:51:13 -08:00
|
|
|
|
case '_email_verify_token':
|
2016-01-28 10:58:12 -08:00
|
|
|
|
case '_perishable_token':
|
2016-02-22 12:14:45 -08:00
|
|
|
|
case '_tombstone':
|
2016-01-28 10:58:12 -08:00
|
|
|
|
break;
|
|
|
|
|
|
case '_session_token':
|
|
|
|
|
|
restObject['sessionToken'] = mongoObject[key];
|
|
|
|
|
|
break;
|
|
|
|
|
|
case 'updatedAt':
|
|
|
|
|
|
case '_updated_at':
|
|
|
|
|
|
restObject['updatedAt'] = Parse._encode(new Date(mongoObject[key])).iso;
|
|
|
|
|
|
break;
|
|
|
|
|
|
case 'createdAt':
|
|
|
|
|
|
case '_created_at':
|
|
|
|
|
|
restObject['createdAt'] = Parse._encode(new Date(mongoObject[key])).iso;
|
|
|
|
|
|
break;
|
2016-01-30 15:50:54 -08:00
|
|
|
|
case 'expiresAt':
|
|
|
|
|
|
case '_expiresAt':
|
2016-03-02 20:32:06 -08:00
|
|
|
|
restObject['expiresAt'] = Parse._encode(new Date(mongoObject[key]));
|
2016-01-30 15:50:54 -08:00
|
|
|
|
break;
|
2016-01-28 10:58:12 -08:00
|
|
|
|
default:
|
2016-02-04 14:03:39 -05:00
|
|
|
|
// Check other auth data keys
|
|
|
|
|
|
var authDataMatch = key.match(/^_auth_data_([a-zA-Z0-9_]+)$/);
|
|
|
|
|
|
if (authDataMatch) {
|
|
|
|
|
|
var provider = authDataMatch[1];
|
|
|
|
|
|
restObject['authData'] = restObject['authData'] || {};
|
|
|
|
|
|
restObject['authData'][provider] = mongoObject[key];
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
2016-02-16 23:43:09 -08:00
|
|
|
|
|
2016-01-28 10:58:12 -08:00
|
|
|
|
if (key.indexOf('_p_') == 0) {
|
|
|
|
|
|
var newKey = key.substring(3);
|
|
|
|
|
|
var expected;
|
|
|
|
|
|
if (schema && schema.getExpectedType) {
|
|
|
|
|
|
expected = schema.getExpectedType(className, newKey);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!expected) {
|
2016-03-26 13:47:44 -04:00
|
|
|
|
log.info('transform.js',
|
2016-01-28 10:58:12 -08:00
|
|
|
|
'Found a pointer column not in the schema, dropping it.',
|
|
|
|
|
|
className, newKey);
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
2016-04-12 17:39:27 -04:00
|
|
|
|
if (expected && expected.type !== 'Pointer') {
|
2016-03-26 13:47:44 -04:00
|
|
|
|
log.info('transform.js', 'Found a pointer in a non-pointer column, dropping it.', className, key);
|
2016-01-28 10:58:12 -08:00
|
|
|
|
break;
|
|
|
|
|
|
}
|
2016-02-01 00:40:01 -08:00
|
|
|
|
if (mongoObject[key] === null) {
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
2016-01-28 10:58:12 -08:00
|
|
|
|
var objData = mongoObject[key].split('$');
|
2016-04-12 17:39:27 -04:00
|
|
|
|
var newClass = (expected ? expected.targetClass : objData[0]);
|
2016-01-28 10:58:12 -08:00
|
|
|
|
if (objData[0] !== newClass) {
|
|
|
|
|
|
throw 'pointer to incorrect className';
|
|
|
|
|
|
}
|
|
|
|
|
|
restObject[newKey] = {
|
|
|
|
|
|
__type: 'Pointer',
|
|
|
|
|
|
className: objData[0],
|
|
|
|
|
|
objectId: objData[1]
|
|
|
|
|
|
};
|
|
|
|
|
|
break;
|
2016-02-20 09:45:09 -05:00
|
|
|
|
} else if (!isNestedObject && key[0] == '_' && key != '__type') {
|
2016-01-28 10:58:12 -08:00
|
|
|
|
throw ('bad key in untransform: ' + key);
|
|
|
|
|
|
} else {
|
2016-02-03 21:30:21 -08:00
|
|
|
|
var expectedType = schema.getExpectedType(className, key);
|
|
|
|
|
|
var value = mongoObject[key];
|
2016-04-12 17:39:27 -04:00
|
|
|
|
if (expectedType && expectedType.type === 'File' && FileCoder.isValidDatabaseObject(value)) {
|
2016-02-03 21:30:21 -08:00
|
|
|
|
restObject[key] = FileCoder.databaseToJSON(value);
|
2016-01-28 10:58:12 -08:00
|
|
|
|
break;
|
|
|
|
|
|
}
|
2016-04-12 17:39:27 -04:00
|
|
|
|
if (expectedType && expectedType.type === 'GeoPoint' && GeoPointCoder.isValidDatabaseObject(value)) {
|
2016-02-03 21:30:21 -08:00
|
|
|
|
restObject[key] = GeoPointCoder.databaseToJSON(value);
|
2016-01-28 10:58:12 -08:00
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2016-04-08 16:06:52 -07:00
|
|
|
|
restObject[key] = untransformObject(schema, className, mongoObject[key], true);
|
2016-01-28 10:58:12 -08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2016-04-08 07:42:02 -04:00
|
|
|
|
|
|
|
|
|
|
if (!isNestedObject) {
|
|
|
|
|
|
let relationFields = schema.getRelationFields(className);
|
|
|
|
|
|
Object.assign(restObject, relationFields);
|
|
|
|
|
|
}
|
2016-01-28 10:58:12 -08:00
|
|
|
|
return restObject;
|
|
|
|
|
|
default:
|
|
|
|
|
|
throw 'unknown js type';
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-04-14 19:24:56 -04:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-02-03 21:30:21 -08:00
|
|
|
|
var DateCoder = {
|
|
|
|
|
|
JSONToDatabase(json) {
|
|
|
|
|
|
return new Date(json.iso);
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
isValidJSON(value) {
|
|
|
|
|
|
return (typeof value === 'object' &&
|
|
|
|
|
|
value !== null &&
|
|
|
|
|
|
value.__type === 'Date'
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
var BytesCoder = {
|
|
|
|
|
|
databaseToJSON(object) {
|
|
|
|
|
|
return {
|
|
|
|
|
|
__type: 'Bytes',
|
|
|
|
|
|
base64: object.buffer.toString('base64')
|
|
|
|
|
|
};
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
isValidDatabaseObject(object) {
|
|
|
|
|
|
return (object instanceof mongodb.Binary);
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
JSONToDatabase(json) {
|
|
|
|
|
|
return new mongodb.Binary(new Buffer(json.base64, 'base64'));
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
isValidJSON(value) {
|
|
|
|
|
|
return (typeof value === 'object' &&
|
|
|
|
|
|
value !== null &&
|
|
|
|
|
|
value.__type === 'Bytes'
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
var GeoPointCoder = {
|
|
|
|
|
|
databaseToJSON(object) {
|
|
|
|
|
|
return {
|
|
|
|
|
|
__type: 'GeoPoint',
|
|
|
|
|
|
latitude: object[1],
|
|
|
|
|
|
longitude: object[0]
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
isValidDatabaseObject(object) {
|
|
|
|
|
|
return (object instanceof Array &&
|
|
|
|
|
|
object.length == 2
|
|
|
|
|
|
);
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
JSONToDatabase(json) {
|
|
|
|
|
|
return [ json.longitude, json.latitude ];
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
isValidJSON(value) {
|
|
|
|
|
|
return (typeof value === 'object' &&
|
|
|
|
|
|
value !== null &&
|
|
|
|
|
|
value.__type === 'GeoPoint'
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
var FileCoder = {
|
|
|
|
|
|
databaseToJSON(object) {
|
|
|
|
|
|
return {
|
|
|
|
|
|
__type: 'File',
|
|
|
|
|
|
name: object
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
isValidDatabaseObject(object) {
|
|
|
|
|
|
return (typeof object === 'string');
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
JSONToDatabase(json) {
|
|
|
|
|
|
return json.name;
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
isValidJSON(value) {
|
|
|
|
|
|
return (typeof value === 'object' &&
|
|
|
|
|
|
value !== null &&
|
|
|
|
|
|
value.__type === 'File'
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2016-01-28 10:58:12 -08:00
|
|
|
|
module.exports = {
|
2016-04-14 19:24:56 -04:00
|
|
|
|
transformKey,
|
2016-04-20 13:35:48 -07:00
|
|
|
|
parseObjectToMongoObjectForCreate,
|
2016-04-14 19:24:56 -04:00
|
|
|
|
transformUpdate,
|
|
|
|
|
|
transformWhere,
|
|
|
|
|
|
transformSelect,
|
|
|
|
|
|
transformDontSelect,
|
|
|
|
|
|
transformInQuery,
|
|
|
|
|
|
transformNotInQuery,
|
|
|
|
|
|
untransformObject
|
2016-01-28 10:58:12 -08:00
|
|
|
|
};
|