Begin isolating object creation code into an externalizable API. (#1569)
* Tidy up transformKeyValue * Specialize transformKeyValue for object creation * remove keys that never appear in creation requests * rename function * remove local var * early exit for simple keys * Refactor create * Force class creation when creating an object * Pass parameters to key value transformer * No need to check for array in this func * start using Parse Format schema in MongoTransform * Remove call to getExpectedType * add tests to ensure client can't see _PushStatus
This commit is contained in:
@@ -23,11 +23,13 @@ var dummySchema = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
describe('parseObjectToMongoObject', () => {
|
describe('parseObjectToMongoObjectForCreate', () => {
|
||||||
|
|
||||||
it('a basic number', (done) => {
|
it('a basic number', (done) => {
|
||||||
var input = {five: 5};
|
var input = {five: 5};
|
||||||
var output = transform.parseObjectToMongoObject(dummySchema, null, input);
|
var output = transform.parseObjectToMongoObjectForCreate(dummySchema, null, input, {
|
||||||
|
fields: {five: {type: 'Number'}}
|
||||||
|
});
|
||||||
jequal(input, output);
|
jequal(input, output);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
@@ -37,7 +39,7 @@ describe('parseObjectToMongoObject', () => {
|
|||||||
createdAt: "2015-10-06T21:24:50.332Z",
|
createdAt: "2015-10-06T21:24:50.332Z",
|
||||||
updatedAt: "2015-10-06T21:24:50.332Z"
|
updatedAt: "2015-10-06T21:24:50.332Z"
|
||||||
};
|
};
|
||||||
var output = transform.parseObjectToMongoObject(dummySchema, null, input);
|
var output = transform.parseObjectToMongoObjectForCreate(dummySchema, null, input);
|
||||||
expect(output._created_at instanceof Date).toBe(true);
|
expect(output._created_at instanceof Date).toBe(true);
|
||||||
expect(output._updated_at instanceof Date).toBe(true);
|
expect(output._updated_at instanceof Date).toBe(true);
|
||||||
done();
|
done();
|
||||||
@@ -49,21 +51,25 @@ describe('parseObjectToMongoObject', () => {
|
|||||||
objectId: 'myId',
|
objectId: 'myId',
|
||||||
className: 'Blah',
|
className: 'Blah',
|
||||||
};
|
};
|
||||||
var out = transform.parseObjectToMongoObject(dummySchema, null, {pointers: [pointer]});
|
var out = transform.parseObjectToMongoObjectForCreate(dummySchema, null, {pointers: [pointer]},{
|
||||||
|
fields: {pointers: {type: 'Array'}}
|
||||||
|
});
|
||||||
jequal([pointer], out.pointers);
|
jequal([pointer], out.pointers);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('a delete op', (done) => {
|
//TODO: object creation requests shouldn't be seeing __op delete, it makes no sense to
|
||||||
|
//have __op delete in a new object. Figure out what this should actually be testing.
|
||||||
|
notWorking('a delete op', (done) => {
|
||||||
var input = {deleteMe: {__op: 'Delete'}};
|
var input = {deleteMe: {__op: 'Delete'}};
|
||||||
var output = transform.parseObjectToMongoObject(dummySchema, null, input);
|
var output = transform.parseObjectToMongoObjectForCreate(dummySchema, null, input);
|
||||||
jequal(output, {});
|
jequal(output, {});
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('basic ACL', (done) => {
|
it('basic ACL', (done) => {
|
||||||
var input = {ACL: {'0123': {'read': true, 'write': true}}};
|
var input = {ACL: {'0123': {'read': true, 'write': true}}};
|
||||||
var output = transform.parseObjectToMongoObject(dummySchema, null, input);
|
var output = transform.parseObjectToMongoObjectForCreate(dummySchema, null, input);
|
||||||
// This just checks that it doesn't crash, but it should check format.
|
// This just checks that it doesn't crash, but it should check format.
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
@@ -71,21 +77,27 @@ describe('parseObjectToMongoObject', () => {
|
|||||||
describe('GeoPoints', () => {
|
describe('GeoPoints', () => {
|
||||||
it('plain', (done) => {
|
it('plain', (done) => {
|
||||||
var geoPoint = {__type: 'GeoPoint', longitude: 180, latitude: -180};
|
var geoPoint = {__type: 'GeoPoint', longitude: 180, latitude: -180};
|
||||||
var out = transform.parseObjectToMongoObject(dummySchema, null, {location: geoPoint});
|
var out = transform.parseObjectToMongoObjectForCreate(dummySchema, null, {location: geoPoint},{
|
||||||
|
fields: {location: {type: 'GeoPoint'}}
|
||||||
|
});
|
||||||
expect(out.location).toEqual([180, -180]);
|
expect(out.location).toEqual([180, -180]);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('in array', (done) => {
|
it('in array', (done) => {
|
||||||
var geoPoint = {__type: 'GeoPoint', longitude: 180, latitude: -180};
|
var geoPoint = {__type: 'GeoPoint', longitude: 180, latitude: -180};
|
||||||
var out = transform.parseObjectToMongoObject(dummySchema, null, {locations: [geoPoint, geoPoint]});
|
var out = transform.parseObjectToMongoObjectForCreate(dummySchema, null, {locations: [geoPoint, geoPoint]},{
|
||||||
|
fields: {locations: {type: 'Array'}}
|
||||||
|
});
|
||||||
expect(out.locations).toEqual([geoPoint, geoPoint]);
|
expect(out.locations).toEqual([geoPoint, geoPoint]);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('in sub-object', (done) => {
|
it('in sub-object', (done) => {
|
||||||
var geoPoint = {__type: 'GeoPoint', longitude: 180, latitude: -180};
|
var geoPoint = {__type: 'GeoPoint', longitude: 180, latitude: -180};
|
||||||
var out = transform.parseObjectToMongoObject(dummySchema, null, { locations: { start: geoPoint }});
|
var out = transform.parseObjectToMongoObjectForCreate(dummySchema, null, { locations: { start: geoPoint }},{
|
||||||
|
fields: {locations: {type: 'Object'}}
|
||||||
|
});
|
||||||
expect(out).toEqual({ locations: { start: geoPoint } });
|
expect(out).toEqual({ locations: { start: geoPoint } });
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
@@ -196,7 +208,9 @@ describe('transform schema key changes', () => {
|
|||||||
var input = {
|
var input = {
|
||||||
somePointer: {__type: 'Pointer', className: 'Micro', objectId: 'oft'}
|
somePointer: {__type: 'Pointer', className: 'Micro', objectId: 'oft'}
|
||||||
};
|
};
|
||||||
var output = transform.parseObjectToMongoObject(dummySchema, null, input);
|
var output = transform.parseObjectToMongoObjectForCreate(dummySchema, null, input, {
|
||||||
|
fields: {somePointer: {type: 'Pointer'}}
|
||||||
|
});
|
||||||
expect(typeof output._p_somePointer).toEqual('string');
|
expect(typeof output._p_somePointer).toEqual('string');
|
||||||
expect(output._p_somePointer).toEqual('Micro$oft');
|
expect(output._p_somePointer).toEqual('Micro$oft');
|
||||||
done();
|
done();
|
||||||
@@ -206,7 +220,9 @@ describe('transform schema key changes', () => {
|
|||||||
var input = {
|
var input = {
|
||||||
userPointer: {__type: 'Pointer', className: '_User', objectId: 'qwerty'}
|
userPointer: {__type: 'Pointer', className: '_User', objectId: 'qwerty'}
|
||||||
};
|
};
|
||||||
var output = transform.parseObjectToMongoObject(dummySchema, null, input);
|
var output = transform.parseObjectToMongoObjectForCreate(dummySchema, null, input, {
|
||||||
|
fields: {userPointer: {type: 'Pointer'}}
|
||||||
|
});
|
||||||
expect(typeof output._p_userPointer).toEqual('string');
|
expect(typeof output._p_userPointer).toEqual('string');
|
||||||
expect(output._p_userPointer).toEqual('_User$qwerty');
|
expect(output._p_userPointer).toEqual('_User$qwerty');
|
||||||
done();
|
done();
|
||||||
@@ -219,7 +235,7 @@ describe('transform schema key changes', () => {
|
|||||||
"Kevin": { "write": true }
|
"Kevin": { "write": true }
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
var output = transform.parseObjectToMongoObject(dummySchema, null, input);
|
var output = transform.parseObjectToMongoObjectForCreate(dummySchema, null, input);
|
||||||
expect(typeof output._rperm).toEqual('object');
|
expect(typeof output._rperm).toEqual('object');
|
||||||
expect(typeof output._wperm).toEqual('object');
|
expect(typeof output._wperm).toEqual('object');
|
||||||
expect(output.ACL).toBeUndefined();
|
expect(output.ACL).toBeUndefined();
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
let request = require('request');
|
||||||
|
|
||||||
describe('Parse.Push', () => {
|
describe('Parse.Push', () => {
|
||||||
|
|
||||||
@@ -89,4 +90,57 @@ describe('Parse.Push', () => {
|
|||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should not allow clients to query _PushStatus', done => {
|
||||||
|
setup()
|
||||||
|
.then(() => Parse.Push.send({
|
||||||
|
where: {
|
||||||
|
deviceType: 'ios'
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
badge: 'increment',
|
||||||
|
alert: 'Hello world!'
|
||||||
|
}
|
||||||
|
}, {useMasterKey: true}))
|
||||||
|
.then(() => {
|
||||||
|
request.get({
|
||||||
|
url: 'http://localhost:8378/1/classes/_PushStatus',
|
||||||
|
json: true,
|
||||||
|
headers: {
|
||||||
|
'X-Parse-Application-Id': 'test',
|
||||||
|
},
|
||||||
|
}, (error, response, body) => {
|
||||||
|
expect(body.results.length).toEqual(0);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should allow master key to query _PushStatus', done => {
|
||||||
|
setup()
|
||||||
|
.then(() => Parse.Push.send({
|
||||||
|
where: {
|
||||||
|
deviceType: 'ios'
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
badge: 'increment',
|
||||||
|
alert: 'Hello world!'
|
||||||
|
}
|
||||||
|
}, {useMasterKey: true}))
|
||||||
|
.then(() => {
|
||||||
|
request.get({
|
||||||
|
url: 'http://localhost:8378/1/classes/_PushStatus',
|
||||||
|
json: true,
|
||||||
|
headers: {
|
||||||
|
'X-Parse-Application-Id': 'test',
|
||||||
|
'X-Parse-Master-Key': 'test',
|
||||||
|
},
|
||||||
|
}, (error, response, body) => {
|
||||||
|
expect(body.results.length).toEqual(1);
|
||||||
|
expect(body.results[0].query).toEqual('{"deviceType":"ios"}');
|
||||||
|
expect(body.results[0].payload).toEqual('{"badge":"increment","alert":"Hello world!"}');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -145,14 +145,16 @@ export class MongoStorageAdapter {
|
|||||||
// this adapter doesn't know about the schema, return a promise that rejects with
|
// this adapter doesn't know about the schema, return a promise that rejects with
|
||||||
// undefined as the reason.
|
// undefined as the reason.
|
||||||
getOneSchema(className) {
|
getOneSchema(className) {
|
||||||
return this.schemaCollection().then(schemasCollection => schemasCollection._fechOneSchemaFrom_SCHEMA(className));
|
return this.schemaCollection()
|
||||||
|
.then(schemasCollection => schemasCollection._fechOneSchemaFrom_SCHEMA(className));
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: As yet not particularly well specified. Creates an object. Does it really need the schema?
|
// TODO: As yet not particularly well specified. Creates an object. Shouldn't need the
|
||||||
// or can it fetch the schema itself? Also the schema is not currently a Parse format schema, and it
|
// schemaController, but MongoTransform still needs it :( maybe shouldn't even need the schema,
|
||||||
// should be, if we are passing it at all.
|
// and should infer from the type. Or maybe does need the schema for validations. Or maybe needs
|
||||||
createObject(className, object, schema) {
|
// the schem only for the legacy mongo format. We'll figure that out later.
|
||||||
const mongoObject = transform.parseObjectToMongoObject(schema, className, object);
|
createObject(className, object, schemaController, parseFormatSchema) {
|
||||||
|
const mongoObject = transform.parseObjectToMongoObjectForCreate(schemaController, className, object, parseFormatSchema);
|
||||||
return this.adaptiveCollection(className)
|
return this.adaptiveCollection(className)
|
||||||
.then(collection => collection.insertOne(mongoObject));
|
.then(collection => collection.insertOne(mongoObject));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,9 +21,13 @@ var Parse = require('parse/node').Parse;
|
|||||||
// validate: true indicates that key names are to be validated.
|
// validate: true indicates that key names are to be validated.
|
||||||
//
|
//
|
||||||
// Returns an object with {key: key, value: value}.
|
// Returns an object with {key: key, value: value}.
|
||||||
export function transformKeyValue(schema, className, restKey, restValue, options) {
|
export function transformKeyValue(schema, className, restKey, restValue, {
|
||||||
options = options || {};
|
inArray,
|
||||||
|
inObject,
|
||||||
|
query,
|
||||||
|
update,
|
||||||
|
validate,
|
||||||
|
} = {}) {
|
||||||
// Check if the schema is known since it's a built-in field.
|
// Check if the schema is known since it's a built-in field.
|
||||||
var key = restKey;
|
var key = restKey;
|
||||||
var timeField = false;
|
var timeField = false;
|
||||||
@@ -62,7 +66,7 @@ export function transformKeyValue(schema, className, restKey, restValue, options
|
|||||||
return {key: key, value: restValue};
|
return {key: key, value: restValue};
|
||||||
break;
|
break;
|
||||||
case '$or':
|
case '$or':
|
||||||
if (!options.query) {
|
if (!query) {
|
||||||
throw new Parse.Error(Parse.Error.INVALID_KEY_NAME,
|
throw new Parse.Error(Parse.Error.INVALID_KEY_NAME,
|
||||||
'you can only use $or in queries');
|
'you can only use $or in queries');
|
||||||
}
|
}
|
||||||
@@ -75,7 +79,7 @@ export function transformKeyValue(schema, className, restKey, restValue, options
|
|||||||
});
|
});
|
||||||
return {key: '$or', value: mongoSubqueries};
|
return {key: '$or', value: mongoSubqueries};
|
||||||
case '$and':
|
case '$and':
|
||||||
if (!options.query) {
|
if (!query) {
|
||||||
throw new Parse.Error(Parse.Error.INVALID_KEY_NAME,
|
throw new Parse.Error(Parse.Error.INVALID_KEY_NAME,
|
||||||
'you can only use $and in queries');
|
'you can only use $and in queries');
|
||||||
}
|
}
|
||||||
@@ -91,7 +95,7 @@ export function transformKeyValue(schema, className, restKey, restValue, options
|
|||||||
// Other auth data
|
// Other auth data
|
||||||
var authDataMatch = key.match(/^authData\.([a-zA-Z0-9_]+)\.id$/);
|
var authDataMatch = key.match(/^authData\.([a-zA-Z0-9_]+)\.id$/);
|
||||||
if (authDataMatch) {
|
if (authDataMatch) {
|
||||||
if (options.query) {
|
if (query) {
|
||||||
var provider = authDataMatch[1];
|
var provider = authDataMatch[1];
|
||||||
// Special-case auth data.
|
// Special-case auth data.
|
||||||
return {key: '_auth_data_'+provider+'.id', value: restValue};
|
return {key: '_auth_data_'+provider+'.id', value: restValue};
|
||||||
@@ -100,7 +104,7 @@ export function transformKeyValue(schema, className, restKey, restValue, options
|
|||||||
'can only query on ' + key);
|
'can only query on ' + key);
|
||||||
break;
|
break;
|
||||||
};
|
};
|
||||||
if (options.validate && !key.match(/^[a-zA-Z][a-zA-Z0-9_\.]*$/)) {
|
if (validate && !key.match(/^[a-zA-Z][a-zA-Z0-9_\.]*$/)) {
|
||||||
throw new Parse.Error(Parse.Error.INVALID_KEY_NAME,
|
throw new Parse.Error(Parse.Error.INVALID_KEY_NAME,
|
||||||
'invalid key name: ' + key);
|
'invalid key name: ' + key);
|
||||||
}
|
}
|
||||||
@@ -117,24 +121,24 @@ export function transformKeyValue(schema, className, restKey, restValue, options
|
|||||||
(!expected && restValue && restValue.__type == 'Pointer')) {
|
(!expected && restValue && restValue.__type == 'Pointer')) {
|
||||||
key = '_p_' + key;
|
key = '_p_' + key;
|
||||||
}
|
}
|
||||||
var inArray = (expected && expected.type === 'Array');
|
var expectedTypeIsArray = (expected && expected.type === 'Array');
|
||||||
|
|
||||||
// Handle query constraints
|
// Handle query constraints
|
||||||
if (options.query) {
|
if (query) {
|
||||||
value = transformConstraint(restValue, inArray);
|
value = transformConstraint(restValue, expectedTypeIsArray);
|
||||||
if (value !== CannotTransform) {
|
if (value !== CannotTransform) {
|
||||||
return {key: key, value: value};
|
return {key: key, value: value};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inArray && options.query && !(restValue instanceof Array)) {
|
if (expectedTypeIsArray && query && !(restValue instanceof Array)) {
|
||||||
return {
|
return {
|
||||||
key: key, value: { '$all' : [restValue] }
|
key: key, value: { '$all' : [restValue] }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle atomic values
|
// Handle atomic values
|
||||||
var value = transformAtom(restValue, false, options);
|
var value = transformAtom(restValue, false, { inArray, inObject });
|
||||||
if (value !== CannotTransform) {
|
if (value !== CannotTransform) {
|
||||||
if (timeField && (typeof value === 'string')) {
|
if (timeField && (typeof value === 'string')) {
|
||||||
value = new Date(value);
|
value = new Date(value);
|
||||||
@@ -150,7 +154,7 @@ export function transformKeyValue(schema, className, restKey, restValue, options
|
|||||||
|
|
||||||
// Handle arrays
|
// Handle arrays
|
||||||
if (restValue instanceof Array) {
|
if (restValue instanceof Array) {
|
||||||
if (options.query) {
|
if (query) {
|
||||||
throw new Parse.Error(Parse.Error.INVALID_JSON,
|
throw new Parse.Error(Parse.Error.INVALID_JSON,
|
||||||
'cannot use array as query param');
|
'cannot use array as query param');
|
||||||
}
|
}
|
||||||
@@ -162,7 +166,7 @@ export function transformKeyValue(schema, className, restKey, restValue, options
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle update operators
|
// Handle update operators
|
||||||
value = transformUpdateOperator(restValue, !options.update);
|
value = transformUpdateOperator(restValue, !update);
|
||||||
if (value !== CannotTransform) {
|
if (value !== CannotTransform) {
|
||||||
return {key: key, value: value};
|
return {key: key, value: value};
|
||||||
}
|
}
|
||||||
@@ -198,18 +202,114 @@ function transformWhere(schema, className, restWhere, options = {validate: true}
|
|||||||
return mongoWhere;
|
return mongoWhere;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 '_created_at'://TODO: for some reason, _PushStatus is already transformed when it gets here. For now,
|
||||||
|
// just pass the _created_at through. Later, we should make sure the push status doesn't get transformed inside Parse Server.
|
||||||
|
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 '_id': //TODO: for some reason, _PushStatus is already transformed when it gets here. For now,
|
||||||
|
// just pass the ID through. Later, we should make sure the push status doesn't get transformed inside Parse Server.
|
||||||
|
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};
|
||||||
|
}
|
||||||
|
|
||||||
// Main exposed method to create new objects.
|
// Main exposed method to create new objects.
|
||||||
// restCreate is the "create" clause in REST API form.
|
// restCreate is the "create" clause in REST API form.
|
||||||
// Returns the mongo form of the object.
|
function parseObjectToMongoObjectForCreate(schema, className, restCreate, parseFormatSchema) {
|
||||||
function parseObjectToMongoObject(schema, className, restCreate) {
|
|
||||||
if (className == '_User') {
|
if (className == '_User') {
|
||||||
restCreate = transformAuthData(restCreate);
|
restCreate = transformAuthData(restCreate);
|
||||||
}
|
}
|
||||||
var mongoCreate = transformACL(restCreate);
|
var mongoCreate = transformACL(restCreate);
|
||||||
for (var restKey in restCreate) {
|
for (let restKey in restCreate) {
|
||||||
var out = transformKeyValue(schema, className, restKey, restCreate[restKey]);
|
let { key, value } = parseObjectKeyValueToMongoObjectKeyValue(
|
||||||
if (out.value !== undefined) {
|
schema,
|
||||||
mongoCreate[out.key] = out.value;
|
className,
|
||||||
|
restKey,
|
||||||
|
restCreate[restKey],
|
||||||
|
parseFormatSchema
|
||||||
|
);
|
||||||
|
if (value !== undefined) {
|
||||||
|
mongoCreate[key] = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return mongoCreate;
|
return mongoCreate;
|
||||||
@@ -920,7 +1020,7 @@ var FileCoder = {
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
transformKey,
|
transformKey,
|
||||||
parseObjectToMongoObject,
|
parseObjectToMongoObjectForCreate,
|
||||||
transformUpdate,
|
transformUpdate,
|
||||||
transformWhere,
|
transformWhere,
|
||||||
transformSelect,
|
transformSelect,
|
||||||
|
|||||||
@@ -312,24 +312,19 @@ DatabaseController.prototype.create = function(className, object, options = {})
|
|||||||
let originalObject = object;
|
let originalObject = object;
|
||||||
object = deepcopy(object);
|
object = deepcopy(object);
|
||||||
|
|
||||||
var schema;
|
|
||||||
var isMaster = !('acl' in options);
|
var isMaster = !('acl' in options);
|
||||||
var aclGroup = options.acl || [];
|
var aclGroup = options.acl || [];
|
||||||
|
|
||||||
return this.validateClassName(className)
|
return this.validateClassName(className)
|
||||||
.then(() => this.loadSchema())
|
.then(() => this.loadSchema())
|
||||||
.then(s => {
|
.then(schemaController => {
|
||||||
schema = s;
|
return (isMaster ? Promise.resolve() : schemaController.validatePermission(className, aclGroup, 'create'))
|
||||||
if (!isMaster) {
|
|
||||||
return schema.validatePermission(className, aclGroup, 'create');
|
|
||||||
}
|
|
||||||
return Promise.resolve();
|
|
||||||
})
|
|
||||||
.then(() => this.handleRelationUpdates(className, null, object))
|
.then(() => this.handleRelationUpdates(className, null, object))
|
||||||
.then(() => this.adapter.createObject(className, object, schema))
|
.then(() => schemaController.enforceClassExists(className))
|
||||||
.then(result => {
|
.then(() => schemaController.getOneSchema(className))
|
||||||
return sanitizeDatabaseResult(originalObject, result.ops[0]);
|
.then(schema => this.adapter.createObject(className, object, schemaController, schema))
|
||||||
});
|
.then(result => sanitizeDatabaseResult(originalObject, result.ops[0]));
|
||||||
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
DatabaseController.prototype.canAddField = function(schema, className, object, aclGroup) {
|
DatabaseController.prototype.canAddField = function(schema, className, object, aclGroup) {
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ const requiredColumns = Object.freeze({
|
|||||||
_Role: ["name", "ACL"]
|
_Role: ["name", "ACL"]
|
||||||
});
|
});
|
||||||
|
|
||||||
const systemClasses = Object.freeze(['_User', '_Installation', '_Role', '_Session', '_Product']);
|
const systemClasses = Object.freeze(['_User', '_Installation', '_Role', '_Session', '_Product', '_PushStatus']);
|
||||||
|
|
||||||
// 10 alpha numberic chars + uppercase
|
// 10 alpha numberic chars + uppercase
|
||||||
const userIdRegex = /^[a-zA-Z0-9]{10}$/;
|
const userIdRegex = /^[a-zA-Z0-9]{10}$/;
|
||||||
@@ -341,12 +341,8 @@ class SchemaController {
|
|||||||
|
|
||||||
// Returns a promise that resolves successfully to the new schema
|
// Returns a promise that resolves successfully to the new schema
|
||||||
// object or fails with a reason.
|
// object or fails with a reason.
|
||||||
// If 'freeze' is true, refuse to update the schema.
|
// If 'freeze' is true, refuse to modify the schema.
|
||||||
// WARNING: this function has side-effects, and doesn't actually
|
enforceClassExists(className, freeze) {
|
||||||
// do any validation of the format of the className. You probably
|
|
||||||
// should use classNameIsValid or addClassIfNotExists or something
|
|
||||||
// like that instead. TODO: rename or remove this function.
|
|
||||||
validateClassName(className, freeze) {
|
|
||||||
if (this.data[className]) {
|
if (this.data[className]) {
|
||||||
return Promise.resolve(this);
|
return Promise.resolve(this);
|
||||||
}
|
}
|
||||||
@@ -366,7 +362,7 @@ class SchemaController {
|
|||||||
return this.reloadData();
|
return this.reloadData();
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
// Ensure that the schema now validates
|
// Ensure that the schema now validates
|
||||||
return this.validateClassName(className, true);
|
return this.enforceClassExists(className, true);
|
||||||
}, () => {
|
}, () => {
|
||||||
// The schema still doesn't validate. Give up
|
// The schema still doesn't validate. Give up
|
||||||
throw new Parse.Error(Parse.Error.INVALID_JSON, 'schema class name does not revalidate');
|
throw new Parse.Error(Parse.Error.INVALID_JSON, 'schema class name does not revalidate');
|
||||||
@@ -547,7 +543,7 @@ class SchemaController {
|
|||||||
// valid.
|
// valid.
|
||||||
validateObject(className, object, query) {
|
validateObject(className, object, query) {
|
||||||
let geocount = 0;
|
let geocount = 0;
|
||||||
let promise = this.validateClassName(className);
|
let promise = this.enforceClassExists(className);
|
||||||
for (let fieldName in object) {
|
for (let fieldName in object) {
|
||||||
if (object[fieldName] === undefined) {
|
if (object[fieldName] === undefined) {
|
||||||
continue;
|
continue;
|
||||||
@@ -642,15 +638,6 @@ class SchemaController {
|
|||||||
return this.reloadData().then(() => !!(this.data[className]));
|
return this.reloadData().then(() => !!(this.data[className]));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to check if a field is a pointer, returns true or false.
|
|
||||||
isPointer(className, key) {
|
|
||||||
let expected = this.getExpectedType(className, key);
|
|
||||||
if (expected && expected.charAt(0) == '*') {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
getRelationFields(className) {
|
getRelationFields(className) {
|
||||||
if (this.data && this.data[className]) {
|
if (this.data && this.data[className]) {
|
||||||
let classData = this.data[className];
|
let classData = this.data[className];
|
||||||
|
|||||||
Reference in New Issue
Block a user