passing another test
This commit is contained in:
@@ -300,7 +300,7 @@ describe('miscellaneous', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
fit('succeed in logging in', function(done) {
|
it('succeed in logging in', function(done) {
|
||||||
createTestUser(function(u) {
|
createTestUser(function(u) {
|
||||||
expect(typeof u.id).toEqual('string');
|
expect(typeof u.id).toEqual('string');
|
||||||
|
|
||||||
@@ -318,7 +318,7 @@ describe('miscellaneous', function() {
|
|||||||
}, fail);
|
}, fail);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('increment with a user object', function(done) {
|
fit('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();
|
||||||
@@ -328,15 +328,14 @@ describe('miscellaneous', function() {
|
|||||||
expect(user.get('foo')).toEqual(1);
|
expect(user.get('foo')).toEqual(1);
|
||||||
user.increment('foo');
|
user.increment('foo');
|
||||||
return user.save();
|
return user.save();
|
||||||
}).then(() => {
|
}).then(() => Parse.User.logOut())
|
||||||
Parse.User.logOut();
|
.then(() => Parse.User.logIn('test', 'moon-y'))
|
||||||
return Parse.User.logIn('test', 'moon-y');
|
.then((user) => {
|
||||||
}).then((user) => {
|
|
||||||
expect(user.get('foo')).toEqual(2);
|
expect(user.get('foo')).toEqual(2);
|
||||||
Parse.User.logOut()
|
Parse.User.logOut()
|
||||||
.then(done);
|
.then(done);
|
||||||
}, (error) => {
|
}, (error) => {
|
||||||
fail(error);
|
fail(JSON.stringify(error));
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -232,7 +232,7 @@ export class MongoStorageAdapter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Atomically finds and updates an object based on query.
|
// Atomically finds and updates an object based on query.
|
||||||
// Resolve with the updated object.
|
// Return value not currently well specified.
|
||||||
findOneAndUpdate(className, schema, query, update) {
|
findOneAndUpdate(className, schema, query, update) {
|
||||||
const mongoUpdate = transformUpdate(className, update, schema);
|
const mongoUpdate = transformUpdate(className, update, schema);
|
||||||
const mongoWhere = transformWhere(className, query, schema);
|
const mongoWhere = transformWhere(className, query, schema);
|
||||||
@@ -255,7 +255,7 @@ export class MongoStorageAdapter {
|
|||||||
let mongoSort = _.mapKeys(sort, (value, fieldName) => transformKey(className, fieldName, schema));
|
let mongoSort = _.mapKeys(sort, (value, fieldName) => transformKey(className, fieldName, schema));
|
||||||
return this._adaptiveCollection(className)
|
return this._adaptiveCollection(className)
|
||||||
.then(collection => collection.find(mongoWhere, { skip, limit, sort: mongoSort }))
|
.then(collection => collection.find(mongoWhere, { skip, limit, sort: mongoSort }))
|
||||||
.then(objects => objects.map(object => mongoObjectToParseObject(className, object, schema)));
|
.then(objects => objects.map(object => mongoObjectToParseObject(className, object, schema)))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a unique index. Unique indexes on nullable fields are not allowed. Since we don't
|
// Create a unique index. Unique indexes on nullable fields are not allowed. Since we don't
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ const parseTypeToPostgresType = type => {
|
|||||||
case 'Object': return 'jsonb';
|
case 'Object': return 'jsonb';
|
||||||
case 'Boolean': return 'boolean';
|
case 'Boolean': return 'boolean';
|
||||||
case 'Pointer': return 'char(10)';
|
case 'Pointer': return 'char(10)';
|
||||||
|
case 'Number': return 'double precision';
|
||||||
case 'Array':
|
case 'Array':
|
||||||
if (type.contents && type.contents.type === 'String') {
|
if (type.contents && type.contents.type === 'String') {
|
||||||
return 'text[]';
|
return 'text[]';
|
||||||
@@ -69,8 +70,8 @@ export class PostgresStorageAdapter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
addFieldIfNotExists(className, fieldName, type) {
|
addFieldIfNotExists(className, fieldName, type) {
|
||||||
// TODO: Doing this in a transaction is probably a good idea.
|
// TODO: Doing this in a transaction might be a good idea.
|
||||||
return this._client.query('ALTER TABLE $<className:name> ADD COLUMN $<fieldName:name> text', { className, fieldName })
|
return this._client.query('ALTER TABLE $<className:name> ADD COLUMN $<fieldName:name> $<postgresType:raw>', { className, fieldName, postgresType: parseTypeToPostgresType(type) })
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
if (error.code === PostgresRelationDoesNotExistError) {
|
if (error.code === PostgresRelationDoesNotExistError) {
|
||||||
return this.createClass(className, { fields: { [fieldName]: type } })
|
return this.createClass(className, { fields: { [fieldName]: type } })
|
||||||
@@ -161,7 +162,7 @@ export class PostgresStorageAdapter {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: remove the mongo format dependency
|
// TODO: remove the mongo format dependency in the return value
|
||||||
createObject(className, schema, object) {
|
createObject(className, schema, object) {
|
||||||
let columnsArray = [];
|
let columnsArray = [];
|
||||||
let valuesArray = [];
|
let valuesArray = [];
|
||||||
@@ -181,7 +182,7 @@ export class PostgresStorageAdapter {
|
|||||||
});
|
});
|
||||||
let columnsPattern = columnsArray.map((col, index) => `$${index + 2}:name`).join(',');
|
let columnsPattern = columnsArray.map((col, index) => `$${index + 2}:name`).join(',');
|
||||||
let valuesPattern = valuesArray.map((val, index) => `$${index + 2 + columnsArray.length}`).join(',');
|
let valuesPattern = valuesArray.map((val, index) => `$${index + 2 + columnsArray.length}`).join(',');
|
||||||
return this._client.query(`INSERT INTO $1~ (${columnsPattern}) VALUES (${valuesPattern})`, [className, ...columnsArray, ...valuesArray])
|
return this._client.query(`INSERT INTO $1:name (${columnsPattern}) VALUES (${valuesPattern})`, [className, ...columnsArray, ...valuesArray])
|
||||||
.then(() => ({ ops: [object] }))
|
.then(() => ({ ops: [object] }))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,9 +205,53 @@ export class PostgresStorageAdapter {
|
|||||||
return Promise.reject('Not implented yet.')
|
return Promise.reject('Not implented yet.')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hopefully we can get rid of this in favor of updateObjectsByQuery.
|
// Return value not currently well specified.
|
||||||
findOneAndUpdate(className, schema, query, update) {
|
findOneAndUpdate(className, schema, query, update) {
|
||||||
return Promise.reject('Not implented yet.')
|
let conditionPatterns = [];
|
||||||
|
let updatePatterns = [];
|
||||||
|
let values = []
|
||||||
|
values.push(className);
|
||||||
|
let index = 2;
|
||||||
|
|
||||||
|
for (let fieldName in update) {
|
||||||
|
let fieldValue = update[fieldName];
|
||||||
|
if (fieldValue.__op === 'Increment') {
|
||||||
|
updatePatterns.push(`$${index}:name = COALESCE($${index}:name, 0) + $${index + 1}`);
|
||||||
|
values.push(fieldName, fieldValue.amount);
|
||||||
|
index += 2;
|
||||||
|
} else if (fieldName === 'updatedAt') { //TODO: stop special casing this. It should check for __type === 'Date' and use .iso
|
||||||
|
updatePatterns.push(`$${index}:name = $${index + 1}`)
|
||||||
|
values.push(fieldName, new Date(fieldValue));
|
||||||
|
index += 2;
|
||||||
|
} else {
|
||||||
|
return Promise.reject(new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, `Postgres doesn't support this type of update yet`));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let fieldName in query) {
|
||||||
|
let fieldValue = query[fieldName];
|
||||||
|
if (typeof fieldValue === 'string') {
|
||||||
|
conditionPatterns.push(`$${index}:name = $${index + 1}`);
|
||||||
|
values.push(fieldName, fieldValue);
|
||||||
|
index += 2;
|
||||||
|
} else if (Array.isArray(fieldValue.$in)) {
|
||||||
|
let inPatterns = [];
|
||||||
|
values.push(fieldName);
|
||||||
|
fieldValue.$in.forEach((listElem, listIndex) => {
|
||||||
|
values.push(listElem);
|
||||||
|
inPatterns.push(`$${index + 1 + listIndex}`);
|
||||||
|
});
|
||||||
|
conditionPatterns.push(`$${index}:name && ARRAY[${inPatterns.join(',')}]`);
|
||||||
|
index = index + 1 + inPatterns.length;
|
||||||
|
} else {
|
||||||
|
return Promise.reject(new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, `Postgres doesn't support this type of request yet`));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let qs = `UPDATE $1:name SET ${updatePatterns.join(',')} WHERE ${conditionPatterns.join(' AND ')} RETURNING *`;
|
||||||
|
return this._client.query(qs, values)
|
||||||
|
.then(val => {
|
||||||
|
return val[0];
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hopefully we can get rid of this. It's only used for config and hooks.
|
// Hopefully we can get rid of this. It's only used for config and hooks.
|
||||||
@@ -216,11 +261,61 @@ export class PostgresStorageAdapter {
|
|||||||
|
|
||||||
// Executes a find. Accepts: className, query in Parse format, and { skip, limit, sort }.
|
// Executes a find. Accepts: className, query in Parse format, and { skip, limit, sort }.
|
||||||
find(className, schema, query, { skip, limit, sort }) {
|
find(className, schema, query, { skip, limit, sort }) {
|
||||||
return this._client.query("SELECT * FROM $<className:name>", { className })
|
let conditionPatterns = [];
|
||||||
|
let values = [];
|
||||||
|
values.push(className);
|
||||||
|
let index = 2;
|
||||||
|
|
||||||
|
for (let fieldName in query) {
|
||||||
|
let fieldValue = query[fieldName];
|
||||||
|
if (typeof fieldValue === 'string') {
|
||||||
|
conditionPatterns.push(`$${index}:name = $${index + 1}`);
|
||||||
|
values.push(fieldName, fieldValue);
|
||||||
|
index += 2;
|
||||||
|
} else if (fieldValue.$ne) {
|
||||||
|
conditionPatterns.push(`$${index}:name <> $${index + 1}`);
|
||||||
|
values.push(fieldName, fieldValue.$ne)
|
||||||
|
index += 2;
|
||||||
|
} else if (Array.isArray(fieldValue.$in)) {
|
||||||
|
let inPatterns = [];
|
||||||
|
values.push(fieldName);
|
||||||
|
fieldValue.$in.forEach((listElem, listIndex) => {
|
||||||
|
values.push(listElem);
|
||||||
|
inPatterns.push(`$${index + 1 + listIndex}`);
|
||||||
|
});
|
||||||
|
conditionPatterns.push(`$${index}:name IN (${inPatterns.join(',')})`);
|
||||||
|
index = index + 1 + inPatterns.length;
|
||||||
|
} else if (fieldValue.__type === 'Pointer') {
|
||||||
|
conditionPatterns.push(`$${index}:name = $${index + 1}`);
|
||||||
|
values.push(fieldName, fieldValue.objectId);
|
||||||
|
index += 2;
|
||||||
|
} else {
|
||||||
|
return Promise.reject(new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, "Postgres doesn't support this query type yet"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._client.query(`SELECT * FROM $1:name WHERE ${conditionPatterns.join(' AND ')}`, values)
|
||||||
.then(results => results.map(object => {
|
.then(results => results.map(object => {
|
||||||
Object.keys(schema.fields).filter(field => schema.fields[field].type === 'Pointer').forEach(fieldName => {
|
Object.keys(schema.fields).filter(field => schema.fields[field].type === 'Pointer').forEach(fieldName => {
|
||||||
object[fieldName] = { objectId: object[fieldName], __type: 'Pointer', className: schema.fields[fieldName].targetClass };
|
object[fieldName] = { objectId: object[fieldName], __type: 'Pointer', className: schema.fields[fieldName].targetClass };
|
||||||
});
|
});
|
||||||
|
//TODO: remove this reliance on the mongo format. DB adapter shouldn't know there is a difference between created at and any other date field.
|
||||||
|
if (object.createdAt) {
|
||||||
|
object.createdAt = object.createdAt.toISOString();
|
||||||
|
}
|
||||||
|
if (object.updatedAt) {
|
||||||
|
object.updatedAt = object.updatedAt.toISOString();
|
||||||
|
}
|
||||||
|
if (object.expiresAt) {
|
||||||
|
object.expiresAt = { __type: 'Date', iso: object.expiresAt.toISOString() };
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let fieldName in object) {
|
||||||
|
if (object[fieldName] === null) {
|
||||||
|
delete object[fieldName];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return object;
|
return object;
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -240,7 +240,7 @@ DatabaseController.prototype.update = function(className, query, update, {
|
|||||||
} else if (upsert) {
|
} else if (upsert) {
|
||||||
return this.adapter.upsertOneObject(className, schema, query, update);
|
return this.adapter.upsertOneObject(className, schema, query, update);
|
||||||
} else {
|
} else {
|
||||||
return this.adapter.findOneAndUpdate(className, schema, query, update);
|
return this.adapter.findOneAndUpdate(className, schema, query, update)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
@@ -645,13 +645,15 @@ DatabaseController.prototype.find = function(className, query, {
|
|||||||
let isMaster = acl === undefined;
|
let isMaster = acl === undefined;
|
||||||
let aclGroup = acl || [];
|
let aclGroup = acl || [];
|
||||||
let op = typeof query.objectId == 'string' && Object.keys(query).length === 1 ? 'get' : 'find';
|
let op = typeof query.objectId == 'string' && Object.keys(query).length === 1 ? 'get' : 'find';
|
||||||
|
let classExists = true;
|
||||||
return this.loadSchema()
|
return this.loadSchema()
|
||||||
.then(schemaController => {
|
.then(schemaController => {
|
||||||
return schemaController.getOneSchema(className)
|
return schemaController.getOneSchema(className)
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
// If the schema doesn't exist, pretend it exists with no fields. This behaviour
|
// Behaviour for non-existent classes is kinda weird on Parse.com. Probably doesn't matter too much.
|
||||||
// will likely need revisiting.
|
// For now, pretend the class exists but has no objects,
|
||||||
if (error === undefined) {
|
if (error === undefined) {
|
||||||
|
classExists = false;
|
||||||
return { fields: {} };
|
return { fields: {} };
|
||||||
}
|
}
|
||||||
throw error;
|
throw error;
|
||||||
@@ -685,10 +687,9 @@ DatabaseController.prototype.find = function(className, query, {
|
|||||||
}
|
}
|
||||||
if (!query) {
|
if (!query) {
|
||||||
if (op == 'get') {
|
if (op == 'get') {
|
||||||
return Promise.reject(new Parse.Error(Parse.Error.OBJECT_NOT_FOUND,
|
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.');
|
||||||
'Object not found.'));
|
|
||||||
} else {
|
} else {
|
||||||
return Promise.resolve([]);
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!isMaster) {
|
if (!isMaster) {
|
||||||
@@ -696,13 +697,21 @@ DatabaseController.prototype.find = function(className, query, {
|
|||||||
}
|
}
|
||||||
validateQuery(query);
|
validateQuery(query);
|
||||||
if (count) {
|
if (count) {
|
||||||
return this.adapter.count(className, schema, query);
|
if (!classExists) {
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
return this.adapter.count(className, schema, query);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return this.adapter.find(className, schema, query, { skip, limit, sort })
|
if (!classExists) {
|
||||||
.then(objects => objects.map(object => {
|
return [];
|
||||||
object = untransformObjectACL(object);
|
} else {
|
||||||
return filterSensitiveData(isMaster, aclGroup, className, object)
|
return this.adapter.find(className, schema, query, { skip, limit, sort })
|
||||||
}));
|
.then(objects => objects.map(object => {
|
||||||
|
object = untransformObjectACL(object);
|
||||||
|
return filterSensitiveData(isMaster, aclGroup, className, object)
|
||||||
|
}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -31,8 +31,7 @@ function RestWrite(config, auth, className, query, data, originalData) {
|
|||||||
this.runOptions = {};
|
this.runOptions = {};
|
||||||
|
|
||||||
if (!query && data.objectId) {
|
if (!query && data.objectId) {
|
||||||
throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, 'objectId ' +
|
throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, 'objectId is an invalid field name.');
|
||||||
'is an invalid field name.');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// When the operation is complete, this.response may have several
|
// When the operation is complete, this.response may have several
|
||||||
@@ -712,8 +711,7 @@ RestWrite.prototype.runDatabaseOperation = function() {
|
|||||||
if (this.className === '_User' &&
|
if (this.className === '_User' &&
|
||||||
this.query &&
|
this.query &&
|
||||||
!this.auth.couldUpdateUserId(this.query.objectId)) {
|
!this.auth.couldUpdateUserId(this.query.objectId)) {
|
||||||
throw new Parse.Error(Parse.Error.SESSION_MISSING,
|
throw new Parse.Error(Parse.Error.SESSION_MISSING, `Cannot modify user ${this.query.objectId}.`);
|
||||||
'cannot modify user ' + this.query.objectId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.className === '_Product' && this.data.download) {
|
if (this.className === '_Product' && this.data.download) {
|
||||||
|
|||||||
@@ -117,8 +117,7 @@ function update(config, auth, className, objectId, restObject) {
|
|||||||
originalRestObject = response.results[0];
|
originalRestObject = response.results[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
var write = new RestWrite(config, auth, className,
|
var write = new RestWrite(config, auth, className, {objectId: objectId}, restObject, originalRestObject);
|
||||||
{objectId: objectId}, restObject, originalRestObject);
|
|
||||||
return write.execute();
|
return write.execute();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user