Schema Cache Improvement 2 (#5616)
* schema hasClass improvement * create object improvement * destroy object * update object * hasClass test rewrite * more tests * improve signing up users
This commit is contained in:
@@ -175,20 +175,20 @@ describe_only(() => {
|
|||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await cacheAdapter.clear();
|
await cacheAdapter.clear();
|
||||||
getSpy = spyOn(cacheAdapter, 'get').and.callThrough();
|
|
||||||
putSpy = spyOn(cacheAdapter, 'put').and.callThrough();
|
|
||||||
await reconfigureServer({
|
await reconfigureServer({
|
||||||
cacheAdapter,
|
cacheAdapter,
|
||||||
enableSingleSchemaCache: true,
|
enableSingleSchemaCache: true,
|
||||||
});
|
});
|
||||||
|
getSpy = spyOn(cacheAdapter, 'get').and.callThrough();
|
||||||
|
putSpy = spyOn(cacheAdapter, 'put').and.callThrough();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('test new object', async () => {
|
it('test new object', async () => {
|
||||||
const object = new TestObject();
|
const object = new TestObject();
|
||||||
object.set('foo', 'bar');
|
object.set('foo', 'bar');
|
||||||
await object.save();
|
await object.save();
|
||||||
expect(getSpy.calls.count()).toBe(4);
|
expect(getSpy.calls.count()).toBe(2);
|
||||||
expect(putSpy.calls.count()).toBe(3);
|
expect(putSpy.calls.count()).toBe(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('test new object multiple fields', async () => {
|
it('test new object multiple fields', async () => {
|
||||||
@@ -200,8 +200,8 @@ describe_only(() => {
|
|||||||
booleanField: true,
|
booleanField: true,
|
||||||
});
|
});
|
||||||
await container.save();
|
await container.save();
|
||||||
expect(getSpy.calls.count()).toBe(4);
|
expect(getSpy.calls.count()).toBe(2);
|
||||||
expect(putSpy.calls.count()).toBe(3);
|
expect(putSpy.calls.count()).toBe(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('test update existing fields', async () => {
|
it('test update existing fields', async () => {
|
||||||
@@ -214,7 +214,57 @@ describe_only(() => {
|
|||||||
|
|
||||||
object.set('foo', 'barz');
|
object.set('foo', 'barz');
|
||||||
await object.save();
|
await object.save();
|
||||||
expect(getSpy.calls.count()).toBe(3);
|
expect(getSpy.calls.count()).toBe(2);
|
||||||
|
expect(putSpy.calls.count()).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('test saveAll / destroyAll', async () => {
|
||||||
|
const object = new TestObject();
|
||||||
|
await object.save();
|
||||||
|
|
||||||
|
getSpy.calls.reset();
|
||||||
|
putSpy.calls.reset();
|
||||||
|
|
||||||
|
const objects = [];
|
||||||
|
for (let i = 0; i < 10; i++) {
|
||||||
|
const object = new TestObject();
|
||||||
|
object.set('number', i);
|
||||||
|
objects.push(object);
|
||||||
|
}
|
||||||
|
await Parse.Object.saveAll(objects);
|
||||||
|
expect(getSpy.calls.count()).toBe(11);
|
||||||
|
expect(putSpy.calls.count()).toBe(10);
|
||||||
|
|
||||||
|
getSpy.calls.reset();
|
||||||
|
putSpy.calls.reset();
|
||||||
|
|
||||||
|
await Parse.Object.destroyAll(objects);
|
||||||
|
expect(getSpy.calls.count()).toBe(11);
|
||||||
|
expect(putSpy.calls.count()).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('test saveAll / destroyAll batch', async () => {
|
||||||
|
const object = new TestObject();
|
||||||
|
await object.save();
|
||||||
|
|
||||||
|
getSpy.calls.reset();
|
||||||
|
putSpy.calls.reset();
|
||||||
|
|
||||||
|
const objects = [];
|
||||||
|
for (let i = 0; i < 10; i++) {
|
||||||
|
const object = new TestObject();
|
||||||
|
object.set('number', i);
|
||||||
|
objects.push(object);
|
||||||
|
}
|
||||||
|
await Parse.Object.saveAll(objects, { batchSize: 5 });
|
||||||
|
expect(getSpy.calls.count()).toBe(12);
|
||||||
|
expect(putSpy.calls.count()).toBe(5);
|
||||||
|
|
||||||
|
getSpy.calls.reset();
|
||||||
|
putSpy.calls.reset();
|
||||||
|
|
||||||
|
await Parse.Object.destroyAll(objects, { batchSize: 5 });
|
||||||
|
expect(getSpy.calls.count()).toBe(12);
|
||||||
expect(putSpy.calls.count()).toBe(0);
|
expect(putSpy.calls.count()).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -228,7 +278,7 @@ describe_only(() => {
|
|||||||
|
|
||||||
object.set('new', 'barz');
|
object.set('new', 'barz');
|
||||||
await object.save();
|
await object.save();
|
||||||
expect(getSpy.calls.count()).toBe(3);
|
expect(getSpy.calls.count()).toBe(2);
|
||||||
expect(putSpy.calls.count()).toBe(1);
|
expect(putSpy.calls.count()).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -248,8 +298,43 @@ describe_only(() => {
|
|||||||
booleanField: true,
|
booleanField: true,
|
||||||
});
|
});
|
||||||
await object.save();
|
await object.save();
|
||||||
|
expect(getSpy.calls.count()).toBe(2);
|
||||||
|
expect(putSpy.calls.count()).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('test user', async () => {
|
||||||
|
const user = new Parse.User();
|
||||||
|
user.setUsername('testing');
|
||||||
|
user.setPassword('testing');
|
||||||
|
await user.signUp();
|
||||||
|
|
||||||
|
expect(getSpy.calls.count()).toBe(6);
|
||||||
|
expect(putSpy.calls.count()).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('test allowClientCreation false', async () => {
|
||||||
|
const object = new TestObject();
|
||||||
|
await object.save();
|
||||||
|
await reconfigureServer({
|
||||||
|
cacheAdapter,
|
||||||
|
enableSingleSchemaCache: true,
|
||||||
|
allowClientClassCreation: false,
|
||||||
|
});
|
||||||
|
getSpy.calls.reset();
|
||||||
|
putSpy.calls.reset();
|
||||||
|
|
||||||
|
object.set('foo', 'bar');
|
||||||
|
await object.save();
|
||||||
expect(getSpy.calls.count()).toBe(3);
|
expect(getSpy.calls.count()).toBe(3);
|
||||||
expect(putSpy.calls.count()).toBe(1);
|
expect(putSpy.calls.count()).toBe(1);
|
||||||
|
|
||||||
|
getSpy.calls.reset();
|
||||||
|
putSpy.calls.reset();
|
||||||
|
|
||||||
|
const query = new Parse.Query(TestObject);
|
||||||
|
await query.get(object.id);
|
||||||
|
expect(getSpy.calls.count()).toBe(3);
|
||||||
|
expect(putSpy.calls.count()).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('test query', async () => {
|
it('test query', async () => {
|
||||||
@@ -266,6 +351,45 @@ describe_only(() => {
|
|||||||
expect(putSpy.calls.count()).toBe(0);
|
expect(putSpy.calls.count()).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('test query include', async () => {
|
||||||
|
const child = new TestObject();
|
||||||
|
await child.save();
|
||||||
|
|
||||||
|
const object = new TestObject();
|
||||||
|
object.set('child', child);
|
||||||
|
await object.save();
|
||||||
|
|
||||||
|
getSpy.calls.reset();
|
||||||
|
putSpy.calls.reset();
|
||||||
|
|
||||||
|
const query = new Parse.Query(TestObject);
|
||||||
|
query.include('child');
|
||||||
|
await query.get(object.id);
|
||||||
|
|
||||||
|
expect(getSpy.calls.count()).toBe(4);
|
||||||
|
expect(putSpy.calls.count()).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('query relation without schema', async () => {
|
||||||
|
const child = new Parse.Object('ChildObject');
|
||||||
|
await child.save();
|
||||||
|
|
||||||
|
const parent = new Parse.Object('ParentObject');
|
||||||
|
const relation = parent.relation('child');
|
||||||
|
relation.add(child);
|
||||||
|
await parent.save();
|
||||||
|
|
||||||
|
getSpy.calls.reset();
|
||||||
|
putSpy.calls.reset();
|
||||||
|
|
||||||
|
const objects = await relation.query().find();
|
||||||
|
expect(objects.length).toBe(1);
|
||||||
|
expect(objects[0].id).toBe(child.id);
|
||||||
|
|
||||||
|
expect(getSpy.calls.count()).toBe(2);
|
||||||
|
expect(putSpy.calls.count()).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
it('test delete object', async () => {
|
it('test delete object', async () => {
|
||||||
const object = new TestObject();
|
const object = new TestObject();
|
||||||
object.set('foo', 'bar');
|
object.set('foo', 'bar');
|
||||||
@@ -275,7 +399,7 @@ describe_only(() => {
|
|||||||
putSpy.calls.reset();
|
putSpy.calls.reset();
|
||||||
|
|
||||||
await object.destroy();
|
await object.destroy();
|
||||||
expect(getSpy.calls.count()).toBe(3);
|
expect(getSpy.calls.count()).toBe(2);
|
||||||
expect(putSpy.calls.count()).toBe(0);
|
expect(putSpy.calls.count()).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -181,31 +181,26 @@ describe('rest query', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('query existent class when disabled client class creation', done => {
|
it('query existent class when disabled client class creation', async () => {
|
||||||
const customConfig = Object.assign({}, config, {
|
const customConfig = Object.assign({}, config, {
|
||||||
allowClientClassCreation: false,
|
allowClientClassCreation: false,
|
||||||
});
|
});
|
||||||
config.database
|
const schema = await config.database.loadSchema();
|
||||||
.loadSchema()
|
const actualSchema = await schema.addClassIfNotExists(
|
||||||
.then(schema => schema.addClassIfNotExists('ClientClassCreation', {}))
|
'ClientClassCreation',
|
||||||
.then(actualSchema => {
|
{}
|
||||||
expect(actualSchema.className).toEqual('ClientClassCreation');
|
);
|
||||||
return rest.find(
|
expect(actualSchema.className).toEqual('ClientClassCreation');
|
||||||
customConfig,
|
|
||||||
auth.nobody(customConfig),
|
await schema.reloadData({ clearCache: true });
|
||||||
'ClientClassCreation',
|
// Should not throw
|
||||||
{}
|
const result = await rest.find(
|
||||||
);
|
customConfig,
|
||||||
})
|
auth.nobody(customConfig),
|
||||||
.then(
|
'ClientClassCreation',
|
||||||
result => {
|
{}
|
||||||
expect(result.results.length).toEqual(0);
|
);
|
||||||
done();
|
expect(result.results.length).toEqual(0);
|
||||||
},
|
|
||||||
() => {
|
|
||||||
fail('Should not throw error');
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('query with wrongly encoded parameter', done => {
|
it('query with wrongly encoded parameter', done => {
|
||||||
|
|||||||
@@ -929,6 +929,7 @@ describe('SchemaController', () => {
|
|||||||
.then(schema => {
|
.then(schema => {
|
||||||
return schema
|
return schema
|
||||||
.addClassIfNotExists('NewClass', {})
|
.addClassIfNotExists('NewClass', {})
|
||||||
|
.then(() => schema.reloadData({ clearCache: true }))
|
||||||
.then(() => {
|
.then(() => {
|
||||||
schema
|
schema
|
||||||
.hasClass('NewClass')
|
.hasClass('NewClass')
|
||||||
|
|||||||
@@ -181,30 +181,25 @@ describe('rest create', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handles create on existent class when disabled client class creation', done => {
|
it('handles create on existent class when disabled client class creation', async () => {
|
||||||
const customConfig = Object.assign({}, config, {
|
const customConfig = Object.assign({}, config, {
|
||||||
allowClientClassCreation: false,
|
allowClientClassCreation: false,
|
||||||
});
|
});
|
||||||
config.database
|
const schema = await config.database.loadSchema();
|
||||||
.loadSchema()
|
const actualSchema = await schema.addClassIfNotExists(
|
||||||
.then(schema => schema.addClassIfNotExists('ClientClassCreation', {}))
|
'ClientClassCreation',
|
||||||
.then(actualSchema => {
|
{}
|
||||||
expect(actualSchema.className).toEqual('ClientClassCreation');
|
);
|
||||||
return rest.create(
|
expect(actualSchema.className).toEqual('ClientClassCreation');
|
||||||
customConfig,
|
|
||||||
auth.nobody(customConfig),
|
await schema.reloadData({ clearCache: true });
|
||||||
'ClientClassCreation',
|
// Should not throw
|
||||||
{}
|
await rest.create(
|
||||||
);
|
customConfig,
|
||||||
})
|
auth.nobody(customConfig),
|
||||||
.then(
|
'ClientClassCreation',
|
||||||
() => {
|
{}
|
||||||
done();
|
);
|
||||||
},
|
|
||||||
() => {
|
|
||||||
fail('Should not throw error');
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handles user signup', done => {
|
it('handles user signup', done => {
|
||||||
|
|||||||
@@ -432,6 +432,15 @@ class DatabaseController {
|
|||||||
return this.loadSchema(options);
|
return this.loadSchema(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
loadSchemaIfNeeded(
|
||||||
|
schemaController: SchemaController.SchemaController,
|
||||||
|
options: LoadSchemaOptions = { clearCache: false }
|
||||||
|
): Promise<SchemaController.SchemaController> {
|
||||||
|
return schemaController
|
||||||
|
? Promise.resolve(schemaController)
|
||||||
|
: this.loadSchema(options);
|
||||||
|
}
|
||||||
|
|
||||||
// Returns a promise for the classname that is related to the given
|
// Returns a promise for the classname that is related to the given
|
||||||
// classname through the key.
|
// classname through the key.
|
||||||
// TODO: make this not in the DatabaseController interface
|
// TODO: make this not in the DatabaseController interface
|
||||||
@@ -477,7 +486,8 @@ class DatabaseController {
|
|||||||
update: any,
|
update: any,
|
||||||
{ acl, many, upsert }: FullQueryOptions = {},
|
{ acl, many, upsert }: FullQueryOptions = {},
|
||||||
skipSanitization: boolean = false,
|
skipSanitization: boolean = false,
|
||||||
validateOnly: boolean = false
|
validateOnly: boolean = false,
|
||||||
|
validSchemaController: SchemaController.SchemaController
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
const originalQuery = query;
|
const originalQuery = query;
|
||||||
const originalUpdate = update;
|
const originalUpdate = update;
|
||||||
@@ -486,141 +496,145 @@ class DatabaseController {
|
|||||||
var relationUpdates = [];
|
var relationUpdates = [];
|
||||||
var isMaster = acl === undefined;
|
var isMaster = acl === undefined;
|
||||||
var aclGroup = acl || [];
|
var aclGroup = acl || [];
|
||||||
return this.loadSchema().then(schemaController => {
|
|
||||||
return (isMaster
|
return this.loadSchemaIfNeeded(validSchemaController).then(
|
||||||
? Promise.resolve()
|
schemaController => {
|
||||||
: schemaController.validatePermission(className, aclGroup, 'update')
|
return (isMaster
|
||||||
)
|
? Promise.resolve()
|
||||||
.then(() => {
|
: schemaController.validatePermission(className, aclGroup, 'update')
|
||||||
relationUpdates = this.collectRelationUpdates(
|
)
|
||||||
className,
|
.then(() => {
|
||||||
originalQuery.objectId,
|
relationUpdates = this.collectRelationUpdates(
|
||||||
update
|
|
||||||
);
|
|
||||||
if (!isMaster) {
|
|
||||||
query = this.addPointerPermissions(
|
|
||||||
schemaController,
|
|
||||||
className,
|
className,
|
||||||
'update',
|
originalQuery.objectId,
|
||||||
query,
|
update
|
||||||
aclGroup
|
|
||||||
);
|
);
|
||||||
}
|
if (!isMaster) {
|
||||||
if (!query) {
|
query = this.addPointerPermissions(
|
||||||
return Promise.resolve();
|
schemaController,
|
||||||
}
|
className,
|
||||||
if (acl) {
|
'update',
|
||||||
query = addWriteACL(query, acl);
|
query,
|
||||||
}
|
aclGroup
|
||||||
validateQuery(query);
|
);
|
||||||
return schemaController
|
}
|
||||||
.getOneSchema(className, true)
|
if (!query) {
|
||||||
.catch(error => {
|
return Promise.resolve();
|
||||||
// If the schema doesn't exist, pretend it exists with no fields. This behavior
|
}
|
||||||
// will likely need revisiting.
|
if (acl) {
|
||||||
if (error === undefined) {
|
query = addWriteACL(query, acl);
|
||||||
return { fields: {} };
|
}
|
||||||
}
|
validateQuery(query);
|
||||||
throw error;
|
return schemaController
|
||||||
})
|
.getOneSchema(className, true)
|
||||||
.then(schema => {
|
.catch(error => {
|
||||||
Object.keys(update).forEach(fieldName => {
|
// If the schema doesn't exist, pretend it exists with no fields. This behavior
|
||||||
if (fieldName.match(/^authData\.([a-zA-Z0-9_]+)\.id$/)) {
|
// will likely need revisiting.
|
||||||
throw new Parse.Error(
|
if (error === undefined) {
|
||||||
Parse.Error.INVALID_KEY_NAME,
|
return { fields: {} };
|
||||||
`Invalid field name for update: ${fieldName}`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
const rootFieldName = getRootFieldName(fieldName);
|
throw error;
|
||||||
if (
|
})
|
||||||
!SchemaController.fieldNameIsValid(rootFieldName) &&
|
.then(schema => {
|
||||||
!isSpecialUpdateKey(rootFieldName)
|
Object.keys(update).forEach(fieldName => {
|
||||||
) {
|
if (fieldName.match(/^authData\.([a-zA-Z0-9_]+)\.id$/)) {
|
||||||
throw new Parse.Error(
|
throw new Parse.Error(
|
||||||
Parse.Error.INVALID_KEY_NAME,
|
Parse.Error.INVALID_KEY_NAME,
|
||||||
`Invalid field name for update: ${fieldName}`
|
`Invalid field name for update: ${fieldName}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const rootFieldName = getRootFieldName(fieldName);
|
||||||
|
if (
|
||||||
|
!SchemaController.fieldNameIsValid(rootFieldName) &&
|
||||||
|
!isSpecialUpdateKey(rootFieldName)
|
||||||
|
) {
|
||||||
|
throw new Parse.Error(
|
||||||
|
Parse.Error.INVALID_KEY_NAME,
|
||||||
|
`Invalid field name for update: ${fieldName}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
for (const updateOperation in update) {
|
||||||
|
if (
|
||||||
|
update[updateOperation] &&
|
||||||
|
typeof update[updateOperation] === 'object' &&
|
||||||
|
Object.keys(update[updateOperation]).some(
|
||||||
|
innerKey =>
|
||||||
|
innerKey.includes('$') || innerKey.includes('.')
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
throw new Parse.Error(
|
||||||
|
Parse.Error.INVALID_NESTED_KEY,
|
||||||
|
"Nested keys should not contain the '$' or '.' characters"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
update = transformObjectACL(update);
|
||||||
|
transformAuthData(className, update, schema);
|
||||||
|
if (validateOnly) {
|
||||||
|
return this.adapter
|
||||||
|
.find(className, schema, query, {})
|
||||||
|
.then(result => {
|
||||||
|
if (!result || !result.length) {
|
||||||
|
throw new Parse.Error(
|
||||||
|
Parse.Error.OBJECT_NOT_FOUND,
|
||||||
|
'Object not found.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (many) {
|
||||||
|
return this.adapter.updateObjectsByQuery(
|
||||||
|
className,
|
||||||
|
schema,
|
||||||
|
query,
|
||||||
|
update
|
||||||
|
);
|
||||||
|
} else if (upsert) {
|
||||||
|
return this.adapter.upsertOneObject(
|
||||||
|
className,
|
||||||
|
schema,
|
||||||
|
query,
|
||||||
|
update
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return this.adapter.findOneAndUpdate(
|
||||||
|
className,
|
||||||
|
schema,
|
||||||
|
query,
|
||||||
|
update
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
for (const updateOperation in update) {
|
})
|
||||||
if (
|
.then((result: any) => {
|
||||||
update[updateOperation] &&
|
if (!result) {
|
||||||
typeof update[updateOperation] === 'object' &&
|
throw new Parse.Error(
|
||||||
Object.keys(update[updateOperation]).some(
|
Parse.Error.OBJECT_NOT_FOUND,
|
||||||
innerKey => innerKey.includes('$') || innerKey.includes('.')
|
'Object not found.'
|
||||||
)
|
);
|
||||||
) {
|
}
|
||||||
throw new Parse.Error(
|
if (validateOnly) {
|
||||||
Parse.Error.INVALID_NESTED_KEY,
|
return result;
|
||||||
"Nested keys should not contain the '$' or '.' characters"
|
}
|
||||||
);
|
return this.handleRelationUpdates(
|
||||||
}
|
className,
|
||||||
}
|
originalQuery.objectId,
|
||||||
update = transformObjectACL(update);
|
update,
|
||||||
transformAuthData(className, update, schema);
|
relationUpdates
|
||||||
if (validateOnly) {
|
).then(() => {
|
||||||
return this.adapter
|
return result;
|
||||||
.find(className, schema, query, {})
|
|
||||||
.then(result => {
|
|
||||||
if (!result || !result.length) {
|
|
||||||
throw new Parse.Error(
|
|
||||||
Parse.Error.OBJECT_NOT_FOUND,
|
|
||||||
'Object not found.'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return {};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (many) {
|
|
||||||
return this.adapter.updateObjectsByQuery(
|
|
||||||
className,
|
|
||||||
schema,
|
|
||||||
query,
|
|
||||||
update
|
|
||||||
);
|
|
||||||
} else if (upsert) {
|
|
||||||
return this.adapter.upsertOneObject(
|
|
||||||
className,
|
|
||||||
schema,
|
|
||||||
query,
|
|
||||||
update
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return this.adapter.findOneAndUpdate(
|
|
||||||
className,
|
|
||||||
schema,
|
|
||||||
query,
|
|
||||||
update
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.then((result: any) => {
|
.then(result => {
|
||||||
if (!result) {
|
if (skipSanitization) {
|
||||||
throw new Parse.Error(
|
return Promise.resolve(result);
|
||||||
Parse.Error.OBJECT_NOT_FOUND,
|
}
|
||||||
'Object not found.'
|
return sanitizeDatabaseResult(originalUpdate, result);
|
||||||
);
|
|
||||||
}
|
|
||||||
if (validateOnly) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
return this.handleRelationUpdates(
|
|
||||||
className,
|
|
||||||
originalQuery.objectId,
|
|
||||||
update,
|
|
||||||
relationUpdates
|
|
||||||
).then(() => {
|
|
||||||
return result;
|
|
||||||
});
|
});
|
||||||
})
|
}
|
||||||
.then(result => {
|
);
|
||||||
if (skipSanitization) {
|
|
||||||
return Promise.resolve(result);
|
|
||||||
}
|
|
||||||
return sanitizeDatabaseResult(originalUpdate, result);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collect all relation-updating operations from a REST-format update.
|
// Collect all relation-updating operations from a REST-format update.
|
||||||
@@ -753,65 +767,68 @@ class DatabaseController {
|
|||||||
destroy(
|
destroy(
|
||||||
className: string,
|
className: string,
|
||||||
query: any,
|
query: any,
|
||||||
{ acl }: QueryOptions = {}
|
{ acl }: QueryOptions = {},
|
||||||
|
validSchemaController: SchemaController.SchemaController
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
const isMaster = acl === undefined;
|
const isMaster = acl === undefined;
|
||||||
const aclGroup = acl || [];
|
const aclGroup = acl || [];
|
||||||
|
|
||||||
return this.loadSchema().then(schemaController => {
|
return this.loadSchemaIfNeeded(validSchemaController).then(
|
||||||
return (isMaster
|
schemaController => {
|
||||||
? Promise.resolve()
|
return (isMaster
|
||||||
: schemaController.validatePermission(className, aclGroup, 'delete')
|
? Promise.resolve()
|
||||||
).then(() => {
|
: schemaController.validatePermission(className, aclGroup, 'delete')
|
||||||
if (!isMaster) {
|
).then(() => {
|
||||||
query = this.addPointerPermissions(
|
if (!isMaster) {
|
||||||
schemaController,
|
query = this.addPointerPermissions(
|
||||||
className,
|
schemaController,
|
||||||
'delete',
|
|
||||||
query,
|
|
||||||
aclGroup
|
|
||||||
);
|
|
||||||
if (!query) {
|
|
||||||
throw new Parse.Error(
|
|
||||||
Parse.Error.OBJECT_NOT_FOUND,
|
|
||||||
'Object not found.'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// delete by query
|
|
||||||
if (acl) {
|
|
||||||
query = addWriteACL(query, acl);
|
|
||||||
}
|
|
||||||
validateQuery(query);
|
|
||||||
return schemaController
|
|
||||||
.getOneSchema(className)
|
|
||||||
.catch(error => {
|
|
||||||
// If the schema doesn't exist, pretend it exists with no fields. This behavior
|
|
||||||
// will likely need revisiting.
|
|
||||||
if (error === undefined) {
|
|
||||||
return { fields: {} };
|
|
||||||
}
|
|
||||||
throw error;
|
|
||||||
})
|
|
||||||
.then(parseFormatSchema =>
|
|
||||||
this.adapter.deleteObjectsByQuery(
|
|
||||||
className,
|
className,
|
||||||
parseFormatSchema,
|
'delete',
|
||||||
query
|
query,
|
||||||
)
|
aclGroup
|
||||||
)
|
);
|
||||||
.catch(error => {
|
if (!query) {
|
||||||
// When deleting sessions while changing passwords, don't throw an error if they don't have any sessions.
|
throw new Parse.Error(
|
||||||
if (
|
Parse.Error.OBJECT_NOT_FOUND,
|
||||||
className === '_Session' &&
|
'Object not found.'
|
||||||
error.code === Parse.Error.OBJECT_NOT_FOUND
|
);
|
||||||
) {
|
|
||||||
return Promise.resolve({});
|
|
||||||
}
|
}
|
||||||
throw error;
|
}
|
||||||
});
|
// delete by query
|
||||||
});
|
if (acl) {
|
||||||
});
|
query = addWriteACL(query, acl);
|
||||||
|
}
|
||||||
|
validateQuery(query);
|
||||||
|
return schemaController
|
||||||
|
.getOneSchema(className)
|
||||||
|
.catch(error => {
|
||||||
|
// If the schema doesn't exist, pretend it exists with no fields. This behavior
|
||||||
|
// will likely need revisiting.
|
||||||
|
if (error === undefined) {
|
||||||
|
return { fields: {} };
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
})
|
||||||
|
.then(parseFormatSchema =>
|
||||||
|
this.adapter.deleteObjectsByQuery(
|
||||||
|
className,
|
||||||
|
parseFormatSchema,
|
||||||
|
query
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.catch(error => {
|
||||||
|
// When deleting sessions while changing passwords, don't throw an error if they don't have any sessions.
|
||||||
|
if (
|
||||||
|
className === '_Session' &&
|
||||||
|
error.code === Parse.Error.OBJECT_NOT_FOUND
|
||||||
|
) {
|
||||||
|
return Promise.resolve({});
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inserts an object into the database.
|
// Inserts an object into the database.
|
||||||
@@ -820,7 +837,8 @@ class DatabaseController {
|
|||||||
className: string,
|
className: string,
|
||||||
object: any,
|
object: any,
|
||||||
{ acl }: QueryOptions = {},
|
{ acl }: QueryOptions = {},
|
||||||
validateOnly: boolean = false
|
validateOnly: boolean = false,
|
||||||
|
validSchemaController: SchemaController.SchemaController
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
// Make a copy of the object, so we don't mutate the incoming data.
|
// Make a copy of the object, so we don't mutate the incoming data.
|
||||||
const originalObject = object;
|
const originalObject = object;
|
||||||
@@ -836,8 +854,9 @@ class DatabaseController {
|
|||||||
null,
|
null,
|
||||||
object
|
object
|
||||||
);
|
);
|
||||||
|
|
||||||
return this.validateClassName(className)
|
return this.validateClassName(className)
|
||||||
.then(() => this.loadSchema())
|
.then(() => this.loadSchemaIfNeeded(validSchemaController))
|
||||||
.then(schemaController => {
|
.then(schemaController => {
|
||||||
return (isMaster
|
return (isMaster
|
||||||
? Promise.resolve()
|
? Promise.resolve()
|
||||||
@@ -1173,7 +1192,8 @@ class DatabaseController {
|
|||||||
pipeline,
|
pipeline,
|
||||||
readPreference,
|
readPreference,
|
||||||
}: any = {},
|
}: any = {},
|
||||||
auth: any = {}
|
auth: any = {},
|
||||||
|
validSchemaController: SchemaController.SchemaController
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
const isMaster = acl === undefined;
|
const isMaster = acl === undefined;
|
||||||
const aclGroup = acl || [];
|
const aclGroup = acl || [];
|
||||||
@@ -1186,153 +1206,157 @@ class DatabaseController {
|
|||||||
op = count === true ? 'count' : op;
|
op = count === true ? 'count' : op;
|
||||||
|
|
||||||
let classExists = true;
|
let classExists = true;
|
||||||
return this.loadSchema().then(schemaController => {
|
return this.loadSchemaIfNeeded(validSchemaController).then(
|
||||||
//Allow volatile classes if querying with Master (for _PushStatus)
|
schemaController => {
|
||||||
//TODO: Move volatile classes concept into mongo adapter, postgres adapter shouldn't care
|
//Allow volatile classes if querying with Master (for _PushStatus)
|
||||||
//that api.parse.com breaks when _PushStatus exists in mongo.
|
//TODO: Move volatile classes concept into mongo adapter, postgres adapter shouldn't care
|
||||||
return schemaController
|
//that api.parse.com breaks when _PushStatus exists in mongo.
|
||||||
.getOneSchema(className, isMaster)
|
return schemaController
|
||||||
.catch(error => {
|
.getOneSchema(className, isMaster)
|
||||||
// Behavior for non-existent classes is kinda weird on Parse.com. Probably doesn't matter too much.
|
.catch(error => {
|
||||||
// For now, pretend the class exists but has no objects,
|
// Behavior for non-existent classes is kinda weird on Parse.com. Probably doesn't matter too much.
|
||||||
if (error === undefined) {
|
// For now, pretend the class exists but has no objects,
|
||||||
classExists = false;
|
if (error === undefined) {
|
||||||
return { fields: {} };
|
classExists = false;
|
||||||
}
|
return { fields: {} };
|
||||||
throw error;
|
|
||||||
})
|
|
||||||
.then(schema => {
|
|
||||||
// Parse.com treats queries on _created_at and _updated_at as if they were queries on createdAt and updatedAt,
|
|
||||||
// so duplicate that behavior here. If both are specified, the correct behavior to match Parse.com is to
|
|
||||||
// use the one that appears first in the sort list.
|
|
||||||
if (sort._created_at) {
|
|
||||||
sort.createdAt = sort._created_at;
|
|
||||||
delete sort._created_at;
|
|
||||||
}
|
|
||||||
if (sort._updated_at) {
|
|
||||||
sort.updatedAt = sort._updated_at;
|
|
||||||
delete sort._updated_at;
|
|
||||||
}
|
|
||||||
const queryOptions = { skip, limit, sort, keys, readPreference };
|
|
||||||
Object.keys(sort).forEach(fieldName => {
|
|
||||||
if (fieldName.match(/^authData\.([a-zA-Z0-9_]+)\.id$/)) {
|
|
||||||
throw new Parse.Error(
|
|
||||||
Parse.Error.INVALID_KEY_NAME,
|
|
||||||
`Cannot sort by ${fieldName}`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
const rootFieldName = getRootFieldName(fieldName);
|
throw error;
|
||||||
if (!SchemaController.fieldNameIsValid(rootFieldName)) {
|
})
|
||||||
throw new Parse.Error(
|
.then(schema => {
|
||||||
Parse.Error.INVALID_KEY_NAME,
|
// Parse.com treats queries on _created_at and _updated_at as if they were queries on createdAt and updatedAt,
|
||||||
`Invalid field name: ${fieldName}.`
|
// so duplicate that behavior here. If both are specified, the correct behavior to match Parse.com is to
|
||||||
);
|
// use the one that appears first in the sort list.
|
||||||
|
if (sort._created_at) {
|
||||||
|
sort.createdAt = sort._created_at;
|
||||||
|
delete sort._created_at;
|
||||||
}
|
}
|
||||||
});
|
if (sort._updated_at) {
|
||||||
return (isMaster
|
sort.updatedAt = sort._updated_at;
|
||||||
? Promise.resolve()
|
delete sort._updated_at;
|
||||||
: schemaController.validatePermission(className, aclGroup, op)
|
}
|
||||||
)
|
const queryOptions = { skip, limit, sort, keys, readPreference };
|
||||||
.then(() => this.reduceRelationKeys(className, query, queryOptions))
|
Object.keys(sort).forEach(fieldName => {
|
||||||
.then(() =>
|
if (fieldName.match(/^authData\.([a-zA-Z0-9_]+)\.id$/)) {
|
||||||
this.reduceInRelation(className, query, schemaController)
|
throw new Parse.Error(
|
||||||
)
|
Parse.Error.INVALID_KEY_NAME,
|
||||||
.then(() => {
|
`Cannot sort by ${fieldName}`
|
||||||
let protectedFields;
|
|
||||||
if (!isMaster) {
|
|
||||||
query = this.addPointerPermissions(
|
|
||||||
schemaController,
|
|
||||||
className,
|
|
||||||
op,
|
|
||||||
query,
|
|
||||||
aclGroup
|
|
||||||
);
|
|
||||||
// ProtectedFields is generated before executing the query so we
|
|
||||||
// can optimize the query using Mongo Projection at a later stage.
|
|
||||||
protectedFields = this.addProtectedFields(
|
|
||||||
schemaController,
|
|
||||||
className,
|
|
||||||
query,
|
|
||||||
aclGroup,
|
|
||||||
auth
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (!query) {
|
const rootFieldName = getRootFieldName(fieldName);
|
||||||
if (op === 'get') {
|
if (!SchemaController.fieldNameIsValid(rootFieldName)) {
|
||||||
throw new Parse.Error(
|
throw new Parse.Error(
|
||||||
Parse.Error.OBJECT_NOT_FOUND,
|
Parse.Error.INVALID_KEY_NAME,
|
||||||
'Object not found.'
|
`Invalid field name: ${fieldName}.`
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!isMaster) {
|
|
||||||
if (op === 'update' || op === 'delete') {
|
|
||||||
query = addWriteACL(query, aclGroup);
|
|
||||||
} else {
|
|
||||||
query = addReadACL(query, aclGroup);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
validateQuery(query);
|
|
||||||
if (count) {
|
|
||||||
if (!classExists) {
|
|
||||||
return 0;
|
|
||||||
} else {
|
|
||||||
return this.adapter.count(
|
|
||||||
className,
|
|
||||||
schema,
|
|
||||||
query,
|
|
||||||
readPreference
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else if (distinct) {
|
|
||||||
if (!classExists) {
|
|
||||||
return [];
|
|
||||||
} else {
|
|
||||||
return this.adapter.distinct(
|
|
||||||
className,
|
|
||||||
schema,
|
|
||||||
query,
|
|
||||||
distinct
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else if (pipeline) {
|
|
||||||
if (!classExists) {
|
|
||||||
return [];
|
|
||||||
} else {
|
|
||||||
return this.adapter.aggregate(
|
|
||||||
className,
|
|
||||||
schema,
|
|
||||||
pipeline,
|
|
||||||
readPreference
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return this.adapter
|
|
||||||
.find(className, schema, query, queryOptions)
|
|
||||||
.then(objects =>
|
|
||||||
objects.map(object => {
|
|
||||||
object = untransformObjectACL(object);
|
|
||||||
return filterSensitiveData(
|
|
||||||
isMaster,
|
|
||||||
aclGroup,
|
|
||||||
className,
|
|
||||||
protectedFields,
|
|
||||||
object
|
|
||||||
);
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.catch(error => {
|
|
||||||
throw new Parse.Error(
|
|
||||||
Parse.Error.INTERNAL_SERVER_ERROR,
|
|
||||||
error
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
return (isMaster
|
||||||
});
|
? Promise.resolve()
|
||||||
|
: schemaController.validatePermission(className, aclGroup, op)
|
||||||
|
)
|
||||||
|
.then(() =>
|
||||||
|
this.reduceRelationKeys(className, query, queryOptions)
|
||||||
|
)
|
||||||
|
.then(() =>
|
||||||
|
this.reduceInRelation(className, query, schemaController)
|
||||||
|
)
|
||||||
|
.then(() => {
|
||||||
|
let protectedFields;
|
||||||
|
if (!isMaster) {
|
||||||
|
query = this.addPointerPermissions(
|
||||||
|
schemaController,
|
||||||
|
className,
|
||||||
|
op,
|
||||||
|
query,
|
||||||
|
aclGroup
|
||||||
|
);
|
||||||
|
// ProtectedFields is generated before executing the query so we
|
||||||
|
// can optimize the query using Mongo Projection at a later stage.
|
||||||
|
protectedFields = this.addProtectedFields(
|
||||||
|
schemaController,
|
||||||
|
className,
|
||||||
|
query,
|
||||||
|
aclGroup,
|
||||||
|
auth
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (!query) {
|
||||||
|
if (op === 'get') {
|
||||||
|
throw new Parse.Error(
|
||||||
|
Parse.Error.OBJECT_NOT_FOUND,
|
||||||
|
'Object not found.'
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!isMaster) {
|
||||||
|
if (op === 'update' || op === 'delete') {
|
||||||
|
query = addWriteACL(query, aclGroup);
|
||||||
|
} else {
|
||||||
|
query = addReadACL(query, aclGroup);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
validateQuery(query);
|
||||||
|
if (count) {
|
||||||
|
if (!classExists) {
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
return this.adapter.count(
|
||||||
|
className,
|
||||||
|
schema,
|
||||||
|
query,
|
||||||
|
readPreference
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if (distinct) {
|
||||||
|
if (!classExists) {
|
||||||
|
return [];
|
||||||
|
} else {
|
||||||
|
return this.adapter.distinct(
|
||||||
|
className,
|
||||||
|
schema,
|
||||||
|
query,
|
||||||
|
distinct
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if (pipeline) {
|
||||||
|
if (!classExists) {
|
||||||
|
return [];
|
||||||
|
} else {
|
||||||
|
return this.adapter.aggregate(
|
||||||
|
className,
|
||||||
|
schema,
|
||||||
|
pipeline,
|
||||||
|
readPreference
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return this.adapter
|
||||||
|
.find(className, schema, query, queryOptions)
|
||||||
|
.then(objects =>
|
||||||
|
objects.map(object => {
|
||||||
|
object = untransformObjectACL(object);
|
||||||
|
return filterSensitiveData(
|
||||||
|
isMaster,
|
||||||
|
aclGroup,
|
||||||
|
className,
|
||||||
|
protectedFields,
|
||||||
|
object
|
||||||
|
);
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.catch(error => {
|
||||||
|
throw new Parse.Error(
|
||||||
|
Parse.Error.INTERNAL_SERVER_ERROR,
|
||||||
|
error
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteSchema(className: string): Promise<void> {
|
deleteSchema(className: string): Promise<void> {
|
||||||
|
|||||||
@@ -646,7 +646,7 @@ export default class SchemaController {
|
|||||||
fields: SchemaFields = {},
|
fields: SchemaFields = {},
|
||||||
classLevelPermissions: any,
|
classLevelPermissions: any,
|
||||||
indexes: any = {}
|
indexes: any = {}
|
||||||
): Promise<void> {
|
): Promise<void | Schema> {
|
||||||
var validationError = this.validateNewClass(
|
var validationError = this.validateNewClass(
|
||||||
className,
|
className,
|
||||||
fields,
|
fields,
|
||||||
@@ -667,11 +667,6 @@ export default class SchemaController {
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
.then(convertAdapterSchemaToParseSchema)
|
.then(convertAdapterSchemaToParseSchema)
|
||||||
.then(res => {
|
|
||||||
return this._cache.clear().then(() => {
|
|
||||||
return Promise.resolve(res);
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
if (error && error.code === Parse.Error.DUPLICATE_VALUE) {
|
if (error && error.code === Parse.Error.DUPLICATE_VALUE) {
|
||||||
throw new Parse.Error(
|
throw new Parse.Error(
|
||||||
@@ -1285,6 +1280,9 @@ export default class SchemaController {
|
|||||||
|
|
||||||
// Checks if a given class is in the schema.
|
// Checks if a given class is in the schema.
|
||||||
hasClass(className: string) {
|
hasClass(className: string) {
|
||||||
|
if (this.schemaData[className]) {
|
||||||
|
return Promise.resolve(true);
|
||||||
|
}
|
||||||
return this.reloadData().then(() => !!this.schemaData[className]);
|
return this.reloadData().then(() => !!this.schemaData[className]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,6 +69,10 @@ function RestWrite(
|
|||||||
|
|
||||||
// The timestamp we'll use for this whole operation
|
// The timestamp we'll use for this whole operation
|
||||||
this.updatedAt = Parse._encode(new Date()).iso;
|
this.updatedAt = Parse._encode(new Date()).iso;
|
||||||
|
|
||||||
|
// Shared SchemaController to be reused to reduce the number of loadSchema() calls per request
|
||||||
|
// Once set the schemaData should be immutable
|
||||||
|
this.validSchemaController = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// A convenient method to perform all the steps of processing the
|
// A convenient method to perform all the steps of processing the
|
||||||
@@ -101,7 +105,8 @@ RestWrite.prototype.execute = function() {
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
return this.validateSchema();
|
return this.validateSchema();
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(schemaController => {
|
||||||
|
this.validSchemaController = schemaController;
|
||||||
return this.setRequiredFieldsIfNeeded();
|
return this.setRequiredFieldsIfNeeded();
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
@@ -614,7 +619,9 @@ RestWrite.prototype._validateUserName = function() {
|
|||||||
.find(
|
.find(
|
||||||
this.className,
|
this.className,
|
||||||
{ username: this.data.username, objectId: { $ne: this.objectId() } },
|
{ username: this.data.username, objectId: { $ne: this.objectId() } },
|
||||||
{ limit: 1 }
|
{ limit: 1 },
|
||||||
|
{},
|
||||||
|
this.validSchemaController
|
||||||
)
|
)
|
||||||
.then(results => {
|
.then(results => {
|
||||||
if (results.length > 0) {
|
if (results.length > 0) {
|
||||||
@@ -645,7 +652,9 @@ RestWrite.prototype._validateEmail = function() {
|
|||||||
.find(
|
.find(
|
||||||
this.className,
|
this.className,
|
||||||
{ email: this.data.email, objectId: { $ne: this.objectId() } },
|
{ email: this.data.email, objectId: { $ne: this.objectId() } },
|
||||||
{ limit: 1 }
|
{ limit: 1 },
|
||||||
|
{},
|
||||||
|
this.validSchemaController
|
||||||
)
|
)
|
||||||
.then(results => {
|
.then(results => {
|
||||||
if (results.length > 0) {
|
if (results.length > 0) {
|
||||||
@@ -854,11 +863,16 @@ RestWrite.prototype.destroyDuplicatedSessions = function() {
|
|||||||
if (!user.objectId) {
|
if (!user.objectId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.config.database.destroy('_Session', {
|
this.config.database.destroy(
|
||||||
user,
|
'_Session',
|
||||||
installationId,
|
{
|
||||||
sessionToken: { $ne: sessionToken },
|
user,
|
||||||
});
|
installationId,
|
||||||
|
sessionToken: { $ne: sessionToken },
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
this.validSchemaController
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handles any followup logic
|
// Handles any followup logic
|
||||||
@@ -1361,7 +1375,15 @@ RestWrite.prototype.runDatabaseOperation = function() {
|
|||||||
return defer.then(() => {
|
return defer.then(() => {
|
||||||
// Run an update
|
// Run an update
|
||||||
return this.config.database
|
return this.config.database
|
||||||
.update(this.className, this.query, this.data, this.runOptions)
|
.update(
|
||||||
|
this.className,
|
||||||
|
this.query,
|
||||||
|
this.data,
|
||||||
|
this.runOptions,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
this.validSchemaController
|
||||||
|
)
|
||||||
.then(response => {
|
.then(response => {
|
||||||
response.updatedAt = this.updatedAt;
|
response.updatedAt = this.updatedAt;
|
||||||
this._updateResponseWithData(response, this.data);
|
this._updateResponseWithData(response, this.data);
|
||||||
@@ -1391,7 +1413,13 @@ RestWrite.prototype.runDatabaseOperation = function() {
|
|||||||
|
|
||||||
// Run a create
|
// Run a create
|
||||||
return this.config.database
|
return this.config.database
|
||||||
.create(this.className, this.data, this.runOptions)
|
.create(
|
||||||
|
this.className,
|
||||||
|
this.data,
|
||||||
|
this.runOptions,
|
||||||
|
false,
|
||||||
|
this.validSchemaController
|
||||||
|
)
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
if (
|
if (
|
||||||
this.className !== '_User' ||
|
this.className !== '_User' ||
|
||||||
|
|||||||
26
src/rest.js
26
src/rest.js
@@ -102,6 +102,7 @@ function del(config, auth, className, objectId) {
|
|||||||
enforceRoleSecurity('delete', className, auth);
|
enforceRoleSecurity('delete', className, auth);
|
||||||
|
|
||||||
let inflatedObject;
|
let inflatedObject;
|
||||||
|
let schemaController;
|
||||||
|
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
@@ -151,8 +152,10 @@ function del(config, auth, className, objectId) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => config.database.loadSchema())
|
||||||
var options = {};
|
.then(s => {
|
||||||
|
schemaController = s;
|
||||||
|
const options = {};
|
||||||
if (!auth.isMaster) {
|
if (!auth.isMaster) {
|
||||||
options.acl = ['*'];
|
options.acl = ['*'];
|
||||||
if (auth.user) {
|
if (auth.user) {
|
||||||
@@ -166,20 +169,19 @@ function del(config, auth, className, objectId) {
|
|||||||
{
|
{
|
||||||
objectId: objectId,
|
objectId: objectId,
|
||||||
},
|
},
|
||||||
options
|
options,
|
||||||
|
schemaController
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
// Notify LiveQuery server if possible
|
// Notify LiveQuery server if possible
|
||||||
config.database.loadSchema().then(schemaController => {
|
const perms = schemaController.getClassLevelPermissions(className);
|
||||||
const perms = schemaController.getClassLevelPermissions(className);
|
config.liveQueryController.onAfterDelete(
|
||||||
config.liveQueryController.onAfterDelete(
|
className,
|
||||||
className,
|
inflatedObject,
|
||||||
inflatedObject,
|
null,
|
||||||
null,
|
perms
|
||||||
perms
|
);
|
||||||
);
|
|
||||||
});
|
|
||||||
return triggers.maybeRunTrigger(
|
return triggers.maybeRunTrigger(
|
||||||
triggers.Types.afterDelete,
|
triggers.Types.afterDelete,
|
||||||
auth,
|
auth,
|
||||||
|
|||||||
Reference in New Issue
Block a user