WIP
This commit is contained in:
@@ -45,7 +45,7 @@ describe('miscellaneous', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
fit('create a valid parse user', function(done) {
|
it('create a valid parse user', function(done) {
|
||||||
createTestUser(function(data) {
|
createTestUser(function(data) {
|
||||||
expect(data.id).not.toBeUndefined();
|
expect(data.id).not.toBeUndefined();
|
||||||
expect(data.getSessionToken()).not.toBeUndefined();
|
expect(data.getSessionToken()).not.toBeUndefined();
|
||||||
@@ -90,7 +90,7 @@ describe('miscellaneous', function() {
|
|||||||
|
|
||||||
it('ensure that email is uniquely indexed', done => {
|
it('ensure that email is uniquely indexed', done => {
|
||||||
let numFailed = 0;
|
let numFailed = 0;
|
||||||
|
let numCreated = 0;
|
||||||
let user1 = new Parse.User();
|
let user1 = new Parse.User();
|
||||||
user1.setPassword('asdf');
|
user1.setPassword('asdf');
|
||||||
user1.setUsername('u1');
|
user1.setUsername('u1');
|
||||||
@@ -207,47 +207,6 @@ describe('miscellaneous', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('ensure that email is uniquely indexed', done => {
|
|
||||||
let numCreated = 0;
|
|
||||||
let numFailed = 0;
|
|
||||||
|
|
||||||
let user1 = new Parse.User();
|
|
||||||
user1.setPassword('asdf');
|
|
||||||
user1.setUsername('u1');
|
|
||||||
user1.setEmail('dupe@dupe.dupe');
|
|
||||||
let p1 = user1.signUp();
|
|
||||||
p1.then(user => {
|
|
||||||
numCreated++;
|
|
||||||
expect(numCreated).toEqual(1);
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
numFailed++;
|
|
||||||
expect(numFailed).toEqual(1);
|
|
||||||
expect(error.code).toEqual(Parse.Error.EMAIL_TAKEN);
|
|
||||||
});
|
|
||||||
|
|
||||||
let user2 = new Parse.User();
|
|
||||||
user2.setPassword('asdf');
|
|
||||||
user2.setUsername('u2');
|
|
||||||
user2.setEmail('dupe@dupe.dupe');
|
|
||||||
let p2 = user2.signUp();
|
|
||||||
p2.then(user => {
|
|
||||||
numCreated++;
|
|
||||||
expect(numCreated).toEqual(1);
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
numFailed++;
|
|
||||||
expect(numFailed).toEqual(1);
|
|
||||||
expect(error.code).toEqual(Parse.Error.EMAIL_TAKEN);
|
|
||||||
});
|
|
||||||
Parse.Promise.all([p1, p2])
|
|
||||||
.then(() => {
|
|
||||||
fail('one of the users should not have been created');
|
|
||||||
done();
|
|
||||||
})
|
|
||||||
.catch(done);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('ensure that if people already have duplicate emails, they can still sign up new users', done => {
|
it('ensure that if people already have duplicate emails, they can still sign up new users', done => {
|
||||||
let config = new Config('test');
|
let config = new Config('test');
|
||||||
// Remove existing data to clear out unique index
|
// Remove existing data to clear out unique index
|
||||||
@@ -318,7 +277,7 @@ describe('miscellaneous', function() {
|
|||||||
}, fail);
|
}, fail);
|
||||||
});
|
});
|
||||||
|
|
||||||
fit('increment with a user object', function(done) {
|
it('increment with a user object', function(done) {
|
||||||
createTestUser().then((user) => {
|
createTestUser().then((user) => {
|
||||||
user.increment('foo');
|
user.increment('foo');
|
||||||
return user.save();
|
return user.save();
|
||||||
|
|||||||
@@ -1186,7 +1186,7 @@ describe('Parse.Query testing', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("regexes with invalid options fail", function(done) {
|
fit("regexes with invalid options fail", function(done) {
|
||||||
var query = new Parse.Query(TestObject);
|
var query = new Parse.Query(TestObject);
|
||||||
query.matches("myString", "FootBall", "some invalid option");
|
query.matches("myString", "FootBall", "some invalid option");
|
||||||
query.find(expectError(Parse.Error.INVALID_QUERY, done));
|
query.find(expectError(Parse.Error.INVALID_QUERY, done));
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ export default class MongoCollection {
|
|||||||
// If there is nothing that matches the query - does insert
|
// If there is nothing that matches the query - does insert
|
||||||
// Postgres Note: `INSERT ... ON CONFLICT UPDATE` that is available since 9.5.
|
// Postgres Note: `INSERT ... ON CONFLICT UPDATE` that is available since 9.5.
|
||||||
upsertOne(query, update) {
|
upsertOne(query, update) {
|
||||||
return this._mongoCollection.update(query, update, { upsert: true });
|
return this._mongoCollection.update(query, update, { upsert: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
updateOne(query, update) {
|
updateOne(query, update) {
|
||||||
|
|||||||
@@ -281,10 +281,14 @@ const parseObjectToMongoObjectForCreate = (className, restCreate, schema) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Use the legacy mongo format for createdAt and updatedAt
|
// Use the legacy mongo format for createdAt and updatedAt
|
||||||
mongoCreate._created_at = mongoCreate.createdAt.iso;
|
if (mongoCreate.createdAt) {
|
||||||
delete mongoCreate.createdAt;
|
mongoCreate._created_at = new Date(mongoCreate.createdAt.iso || mongoCreate.createdAt);
|
||||||
mongoCreate._updated_at = mongoCreate.updatedAt.iso;
|
delete mongoCreate.createdAt;
|
||||||
delete mongoCreate.updatedAt;
|
}
|
||||||
|
if (mongoCreate.updatedAt) {
|
||||||
|
mongoCreate._updated_at = new Date(mongoCreate.updatedAt.iso || mongoCreate.updatedAt);
|
||||||
|
delete mongoCreate.updatedAt;
|
||||||
|
}
|
||||||
|
|
||||||
return mongoCreate;
|
return mongoCreate;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -166,6 +166,11 @@ export class PostgresStorageAdapter {
|
|||||||
createObject(className, schema, object) {
|
createObject(className, schema, object) {
|
||||||
let columnsArray = [];
|
let columnsArray = [];
|
||||||
let valuesArray = [];
|
let valuesArray = [];
|
||||||
|
console.log('creating');
|
||||||
|
console.log(schema);
|
||||||
|
console.log(object);
|
||||||
|
console.log(className);
|
||||||
|
console.log(new Error().stack);
|
||||||
Object.keys(object).forEach(fieldName => {
|
Object.keys(object).forEach(fieldName => {
|
||||||
columnsArray.push(fieldName);
|
columnsArray.push(fieldName);
|
||||||
switch (schema.fields[fieldName].type) {
|
switch (schema.fields[fieldName].type) {
|
||||||
|
|||||||
@@ -648,7 +648,10 @@ DatabaseController.prototype.find = function(className, query, {
|
|||||||
let classExists = true;
|
let classExists = true;
|
||||||
return this.loadSchema()
|
return this.loadSchema()
|
||||||
.then(schemaController => {
|
.then(schemaController => {
|
||||||
return schemaController.getOneSchema(className)
|
//Allow volatile classes if querying with Master (for _PushStatus)
|
||||||
|
//TODO: Move volatile classes concept into mongo adatper, postgres adapter shouldn't care
|
||||||
|
//that api.parse.com breaks when _PushStatus exists in mongo.
|
||||||
|
return schemaController.getOneSchema(className, isMaster)
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
// Behaviour for non-existent classes is kinda weird on Parse.com. Probably doesn't matter too much.
|
// Behaviour for non-existent classes is kinda weird on Parse.com. Probably doesn't matter too much.
|
||||||
// For now, pretend the class exists but has no objects,
|
// For now, pretend the class exists but has no objects,
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ const requiredColumns = Object.freeze({
|
|||||||
|
|
||||||
const systemClasses = Object.freeze(['_User', '_Installation', '_Role', '_Session', '_Product', '_PushStatus']);
|
const systemClasses = Object.freeze(['_User', '_Installation', '_Role', '_Session', '_Product', '_PushStatus']);
|
||||||
|
|
||||||
const volatileClasses = Object.freeze(['_PushStatus']);
|
const volatileClasses = Object.freeze(['_PushStatus', '_Hooks', '_GlobalConfig']);
|
||||||
|
|
||||||
// 10 alpha numberic chars + uppercase
|
// 10 alpha numberic chars + uppercase
|
||||||
const userIdRegex = /^[a-zA-Z0-9]{10}$/;
|
const userIdRegex = /^[a-zA-Z0-9]{10}$/;
|
||||||
@@ -244,6 +244,14 @@ const injectDefaultSchema = schema => ({
|
|||||||
classLevelPermissions: schema.classLevelPermissions,
|
classLevelPermissions: schema.classLevelPermissions,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const dbTypeMatchesObjectType = (dbType, objectType) => {
|
||||||
|
if (dbType.type !== objectType.type) return false;
|
||||||
|
if (dbType.targetClass !== objectType.targetClass) return false;
|
||||||
|
if (dbType === objectType.type) return true;
|
||||||
|
if (dbType.type === objectType.type) return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Stores the entire schema of the app in a weird hybrid format somewhere between
|
// Stores the entire schema of the app in a weird hybrid format somewhere between
|
||||||
// the mongo format and the Parse format. Soon, this will all be Parse format.
|
// the mongo format and the Parse format. Soon, this will all be Parse format.
|
||||||
class SchemaController {
|
class SchemaController {
|
||||||
@@ -358,7 +366,7 @@ class SchemaController {
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
let promises = insertedFields.map(fieldName => {
|
let promises = insertedFields.map(fieldName => {
|
||||||
const type = submittedFields[fieldName];
|
const type = submittedFields[fieldName];
|
||||||
return this.validateField(className, fieldName, type);
|
return this.enforceFieldExists(className, fieldName, type);
|
||||||
});
|
});
|
||||||
return Promise.all(promises);
|
return Promise.all(promises);
|
||||||
})
|
})
|
||||||
@@ -460,49 +468,36 @@ class SchemaController {
|
|||||||
// object if the provided className-fieldName-type tuple is valid.
|
// object if the provided className-fieldName-type tuple is valid.
|
||||||
// The className must already be validated.
|
// The className must already be validated.
|
||||||
// If 'freeze' is true, refuse to update the schema for this field.
|
// If 'freeze' is true, refuse to update the schema for this field.
|
||||||
validateField(className, fieldName, type, freeze) {
|
enforceFieldExists(className, fieldName, type, freeze) {
|
||||||
|
if (fieldName.indexOf(".") > 0) {
|
||||||
|
// subdocument key (x.y) => ok if x is of type 'object'
|
||||||
|
fieldName = fieldName.split(".")[ 0 ];
|
||||||
|
type = 'Object';
|
||||||
|
}
|
||||||
|
if (!fieldNameIsValid(fieldName)) {
|
||||||
|
throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, `Invalid field name: ${fieldName}.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If someone tries to create a new field with null/undefined as the value, return;
|
||||||
|
if (!type) {
|
||||||
|
return Promise.resolve(this);
|
||||||
|
}
|
||||||
|
|
||||||
return this.reloadData().then(() => {
|
return this.reloadData().then(() => {
|
||||||
if (fieldName.indexOf(".") > 0) {
|
let expectedType = this.getExpectedType(className, fieldName);
|
||||||
// subdocument key (x.y) => ok if x is of type 'object'
|
|
||||||
fieldName = fieldName.split(".")[ 0 ];
|
|
||||||
type = 'Object';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!fieldNameIsValid(fieldName)) {
|
|
||||||
throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, `Invalid field name: ${fieldName}.`);
|
|
||||||
}
|
|
||||||
|
|
||||||
let expected = this.data[className][fieldName];
|
|
||||||
if (expected) {
|
|
||||||
expected = (expected === 'map' ? 'Object' : expected);
|
|
||||||
if (expected.type && type.type
|
|
||||||
&& expected.type == type.type
|
|
||||||
&& expected.targetClass == type.targetClass) {
|
|
||||||
return Promise.resolve(this);
|
|
||||||
} else if (expected == type || expected.type == type) {
|
|
||||||
return Promise.resolve(this);
|
|
||||||
} else {
|
|
||||||
throw new Parse.Error(
|
|
||||||
Parse.Error.INCORRECT_TYPE,
|
|
||||||
`schema mismatch for ${className}.${fieldName}; expected ${expected.type || expected} but got ${type}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (freeze) {
|
|
||||||
throw new Parse.Error(Parse.Error.INVALID_JSON, `schema is frozen, cannot add ${fieldName} field`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// We don't have this field, but if the value is null or undefined,
|
|
||||||
// we won't update the schema until we get a value with a type.
|
|
||||||
if (!type) {
|
|
||||||
return Promise.resolve(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof type === 'string') {
|
if (typeof type === 'string') {
|
||||||
type = { type };
|
type = { type };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (expectedType) {
|
||||||
|
if (!dbTypeMatchesObjectType(expectedType, type)) {
|
||||||
|
throw new Parse.Error(
|
||||||
|
Parse.Error.INCORRECT_TYPE,
|
||||||
|
`schema mismatch for ${className}.${fieldName}; expected ${expectedType.type || expectedType} but got ${type}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return this._dbAdapter.addFieldIfNotExists(className, fieldName, type).then(() => {
|
return this._dbAdapter.addFieldIfNotExists(className, fieldName, type).then(() => {
|
||||||
// The update succeeded. Reload the schema
|
// The update succeeded. Reload the schema
|
||||||
return this.reloadData();
|
return this.reloadData();
|
||||||
@@ -513,11 +508,10 @@ class SchemaController {
|
|||||||
return this.reloadData();
|
return this.reloadData();
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
// Ensure that the schema now validates
|
// Ensure that the schema now validates
|
||||||
return this.validateField(className, fieldName, type, true);
|
if (!dbTypeMatchesObjectType(this.getExpectedType(className, fieldName), type)) {
|
||||||
}, (error) => {
|
throw new Parse.Error(Parse.Error.INVALID_JSON, `Could not add field ${fieldName}`);
|
||||||
// The schema still doesn't validate. Give up
|
}
|
||||||
throw new Parse.Error(Parse.Error.INVALID_JSON,
|
return this;
|
||||||
'schema key will not revalidate');
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -674,9 +668,10 @@ class SchemaController {
|
|||||||
|
|
||||||
// Returns the expected type for a className+key combination
|
// Returns the expected type for a className+key combination
|
||||||
// or undefined if the schema is not set
|
// or undefined if the schema is not set
|
||||||
getExpectedType(className, key) {
|
getExpectedType(className, fieldName) {
|
||||||
if (this.data && this.data[className]) {
|
if (this.data && this.data[className]) {
|
||||||
return this.data[className][key];
|
const expectedType = this.data[className][fieldName]
|
||||||
|
return expectedType === 'map' ? 'Object' : expectedType;
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
};
|
};
|
||||||
@@ -727,7 +722,7 @@ function buildMergedSchemaObject(existingFields, putRequest) {
|
|||||||
// validates this field once the schema loads.
|
// validates this field once the schema loads.
|
||||||
function thenValidateField(schemaPromise, className, key, type) {
|
function thenValidateField(schemaPromise, className, key, type) {
|
||||||
return schemaPromise.then((schema) => {
|
return schemaPromise.then((schema) => {
|
||||||
return schema.validateField(className, key, type);
|
return schema.enforceFieldExists(className, key, type);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user