From 96aa29a525560dc8a35d906a7f31346773f60938 Mon Sep 17 00:00:00 2001 From: dblythy Date: Thu, 31 Mar 2022 16:04:21 +1100 Subject: [PATCH 01/12] fix pointers --- spec/CloudCode.spec.js | 26 ++++++++++++++++++++++++++ src/RestWrite.js | 8 +++++++- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/spec/CloudCode.spec.js b/spec/CloudCode.spec.js index faaa6b826a..b2756c5d31 100644 --- a/spec/CloudCode.spec.js +++ b/spec/CloudCode.spec.js @@ -1598,6 +1598,32 @@ describe('Cloud Code', () => { expect(obj.get('count')).toBe(0); }); + it('Pointer should not be cleared by triggers', async () => { + Parse.Cloud.afterSave('MyObject', () => {}); + const foo = await new Parse.Object('Test', { foo: 'bar' }).save(); + const obj = await new Parse.Object('MyObject', { foo }).save(); + const foo2 = obj.get('foo'); + expect(foo2.get('foo')).toBe('bar'); + }); + + it('Can set a pointer in triggers', async () => { + Parse.Cloud.beforeSave('MyObject', () => {}); + Parse.Cloud.afterSave( + 'MyObject', + async ({ object }) => { + const foo = await new Parse.Object('Test', { foo: 'bar' }).save(); + object.set({ foo }); + await object.save(null, { useMasterKey: true }); + }, + { + skipWithMasterKey: true, + } + ); + const obj = await new Parse.Object('MyObject').save(); + const foo2 = obj.get('foo'); + expect(foo2.get('foo')).toBe('bar'); + }); + it('beforeSave should not sanitize database', async done => { const { adapter } = Config.get(Parse.applicationId).database; const spy = spyOn(adapter, 'findOneAndUpdate').and.callThrough(); diff --git a/src/RestWrite.js b/src/RestWrite.js index 3e20328a9a..bf8b2cc7e7 100644 --- a/src/RestWrite.js +++ b/src/RestWrite.js @@ -1556,7 +1556,7 @@ RestWrite.prototype.runAfterSaveTrigger = function () { this.response.response = result; } else { this.response.response = this._updateResponseWithData( - (result || updatedObject)._toFullJSON(), + (result || updatedObject).toJSON(), this.data ); } @@ -1665,6 +1665,12 @@ RestWrite.prototype._updateResponseWithData = function (response, data) { this.storage.fieldsChangedByTrigger.push(key); } } + for (const key in response) { + const value = response[key]; + if (value?.__type === 'Pointer') { + delete response[key]; + } + } if (_.isEmpty(this.storage.fieldsChangedByTrigger)) { return response; } From 3e88cf00b2799cad9cd6f2e74c4eded1cf8412b8 Mon Sep 17 00:00:00 2001 From: dblythy Date: Thu, 31 Mar 2022 16:30:05 +1100 Subject: [PATCH 02/12] check save response --- spec/RestQuery.spec.js | 22 ++++++++++++++++++++++ src/RestWrite.js | 6 +++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/spec/RestQuery.spec.js b/spec/RestQuery.spec.js index c8e3adb49d..deac233915 100644 --- a/spec/RestQuery.spec.js +++ b/spec/RestQuery.spec.js @@ -429,4 +429,26 @@ describe('RestQuery.each', () => { done(); }); }); + + it('test afterSave should not affect save response', async () => { + Parse.Cloud.beforeSave('TestObject2', ({ object }) => { + object.set('addedBeforeSave', true); + }); + Parse.Cloud.afterSave('TestObject2', ({ object }) => { + object.set('addedAfterSave', true); + object.unset('initialToRemove'); + }); + const { response } = await rest.create(config, nobody, 'TestObject2', { + initialSave: true, + initialToRemove: true, + }); + expect(Object.keys(response).sort()).toEqual([ + 'addedAfterSave', + 'addedBeforeSave', + 'createdAt', + 'initialToRemove', + 'objectId', + 'updatedAt', + ]); + }); }); diff --git a/src/RestWrite.js b/src/RestWrite.js index bf8b2cc7e7..0540db70c7 100644 --- a/src/RestWrite.js +++ b/src/RestWrite.js @@ -1666,8 +1666,12 @@ RestWrite.prototype._updateResponseWithData = function (response, data) { } } for (const key in response) { + const skipKeys = ['objectId', 'createdAt', 'updatedAt']; + if (skipKeys.includes(key)) { + continue; + } const value = response[key]; - if (value?.__type === 'Pointer') { + if (value?.__type === 'Pointer' || data[key] === value) { delete response[key]; } } From 41703ca0cc980363dd42ba5e0364db89addbc022 Mon Sep 17 00:00:00 2001 From: dblythy Date: Thu, 31 Mar 2022 18:25:56 +1100 Subject: [PATCH 03/12] Update RestWrite.js --- src/RestWrite.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/RestWrite.js b/src/RestWrite.js index 0540db70c7..e566ce63f1 100644 --- a/src/RestWrite.js +++ b/src/RestWrite.js @@ -1666,7 +1666,7 @@ RestWrite.prototype._updateResponseWithData = function (response, data) { } } for (const key in response) { - const skipKeys = ['objectId', 'createdAt', 'updatedAt']; + const skipKeys = ['objectId', 'createdAt', 'updatedAt', 'username']; if (skipKeys.includes(key)) { continue; } From dd59f2adaf118d4eba39b98bcd250bcb4d44107a Mon Sep 17 00:00:00 2001 From: dblythy Date: Mon, 4 Apr 2022 10:03:52 +1000 Subject: [PATCH 04/12] Update RestWrite.js --- src/RestWrite.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/RestWrite.js b/src/RestWrite.js index e566ce63f1..9fc4b0f734 100644 --- a/src/RestWrite.js +++ b/src/RestWrite.js @@ -1671,7 +1671,7 @@ RestWrite.prototype._updateResponseWithData = function (response, data) { continue; } const value = response[key]; - if (value?.__type === 'Pointer' || data[key] === value) { + if ((value.__type && value.__type === 'Pointer') || data[key] === value) { delete response[key]; } } From 8dbdbbcf9ac2ac7c1a4b4a046b3d0b5c276e9ea8 Mon Sep 17 00:00:00 2001 From: dblythy Date: Tue, 5 Apr 2022 14:56:22 +1000 Subject: [PATCH 05/12] Update RestWrite.js --- src/RestWrite.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/RestWrite.js b/src/RestWrite.js index 9fc4b0f734..1754776ef4 100644 --- a/src/RestWrite.js +++ b/src/RestWrite.js @@ -1667,10 +1667,10 @@ RestWrite.prototype._updateResponseWithData = function (response, data) { } for (const key in response) { const skipKeys = ['objectId', 'createdAt', 'updatedAt', 'username']; - if (skipKeys.includes(key)) { + const value = response[key]; + if (skipKeys.includes(key) || value == null) { continue; } - const value = response[key]; if ((value.__type && value.__type === 'Pointer') || data[key] === value) { delete response[key]; } From 5e53a48cce0931db4821ee93dc9e31797fe573cd Mon Sep 17 00:00:00 2001 From: dblythy Date: Tue, 5 Apr 2022 15:25:22 +1000 Subject: [PATCH 06/12] Update RestWrite.js --- src/RestWrite.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/RestWrite.js b/src/RestWrite.js index 1754776ef4..d4e32fabb8 100644 --- a/src/RestWrite.js +++ b/src/RestWrite.js @@ -1667,11 +1667,11 @@ RestWrite.prototype._updateResponseWithData = function (response, data) { } for (const key in response) { const skipKeys = ['objectId', 'createdAt', 'updatedAt', 'username']; - const value = response[key]; - if (skipKeys.includes(key) || value == null) { + if (skipKeys.includes(key)) { continue; } - if ((value.__type && value.__type === 'Pointer') || data[key] === value) { + const value = response[key]; + if (value == null || (value.__type && value.__type === 'Pointer') || data[key] === value) { delete response[key]; } } From 49f7665a5c210b77832c9058c56140ec2846df61 Mon Sep 17 00:00:00 2001 From: dblythy Date: Wed, 13 Apr 2022 12:45:01 +1000 Subject: [PATCH 07/12] Update RestWrite.js --- src/RestWrite.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/RestWrite.js b/src/RestWrite.js index d4e32fabb8..d03546c4bb 100644 --- a/src/RestWrite.js +++ b/src/RestWrite.js @@ -1665,8 +1665,16 @@ RestWrite.prototype._updateResponseWithData = function (response, data) { this.storage.fieldsChangedByTrigger.push(key); } } + const defaultMandatoryColumnsInResponse = Object.freeze({ + _User: ['username'], + }); + const skipKeys = [ + 'objectId', + 'createdAt', + 'updatedAt', + ...(defaultMandatoryColumnsInResponse[this.className] || []), + ]; for (const key in response) { - const skipKeys = ['objectId', 'createdAt', 'updatedAt', 'username']; if (skipKeys.includes(key)) { continue; } From 9362fbb370f6b4d1facc14347b60a5314721c338 Mon Sep 17 00:00:00 2001 From: dblythy Date: Wed, 11 May 2022 15:00:48 +1000 Subject: [PATCH 08/12] add requiredColumns --- src/Controllers/SchemaController.js | 2 ++ src/RestWrite.js | 6 ++---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Controllers/SchemaController.js b/src/Controllers/SchemaController.js index 9ae6628088..22e4547eb1 100644 --- a/src/Controllers/SchemaController.js +++ b/src/Controllers/SchemaController.js @@ -151,6 +151,7 @@ const defaultColumns: { [string]: SchemaFields } = Object.freeze({ }); const requiredColumns = Object.freeze({ + _User: 'username', _Product: ['productIdentifier', 'icon', 'order', 'title', 'subtitle'], _Role: ['name', 'ACL'], }); @@ -1600,4 +1601,5 @@ export { convertSchemaToAdapterSchema, VolatileClassesSchemas, SchemaController, + requiredColumns, }; diff --git a/src/RestWrite.js b/src/RestWrite.js index d03546c4bb..cbe761e9fe 100644 --- a/src/RestWrite.js +++ b/src/RestWrite.js @@ -15,6 +15,7 @@ var ClientSDK = require('./ClientSDK'); import RestQuery from './RestQuery'; import _ from 'lodash'; import logger from './logger'; +import { requiredColumns } from './Controllers/SchemaController'; // query and data are both provided in REST API format. So data // types are encoded by plain old objects. @@ -1665,14 +1666,11 @@ RestWrite.prototype._updateResponseWithData = function (response, data) { this.storage.fieldsChangedByTrigger.push(key); } } - const defaultMandatoryColumnsInResponse = Object.freeze({ - _User: ['username'], - }); const skipKeys = [ 'objectId', 'createdAt', 'updatedAt', - ...(defaultMandatoryColumnsInResponse[this.className] || []), + ...(requiredColumns[this.className] || []), ]; for (const key in response) { if (skipKeys.includes(key)) { From 519beaebf3df42b8253a48b1279583c27babeda5 Mon Sep 17 00:00:00 2001 From: dblythy Date: Wed, 11 May 2022 20:12:12 +1000 Subject: [PATCH 09/12] Update SchemaController.js --- src/Controllers/SchemaController.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Controllers/SchemaController.js b/src/Controllers/SchemaController.js index 22e4547eb1..4696769054 100644 --- a/src/Controllers/SchemaController.js +++ b/src/Controllers/SchemaController.js @@ -151,7 +151,7 @@ const defaultColumns: { [string]: SchemaFields } = Object.freeze({ }); const requiredColumns = Object.freeze({ - _User: 'username', + _User: ['username'], _Product: ['productIdentifier', 'icon', 'order', 'title', 'subtitle'], _Role: ['name', 'ACL'], }); From b3a136bff29acf3ae02d587b031e11a49be4dc62 Mon Sep 17 00:00:00 2001 From: dblythy Date: Wed, 11 May 2022 20:25:19 +1000 Subject: [PATCH 10/12] rename --- src/Controllers/SchemaController.js | 11 +++++++---- src/RestWrite.js | 4 ++-- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/Controllers/SchemaController.js b/src/Controllers/SchemaController.js index 4696769054..e69a5bb151 100644 --- a/src/Controllers/SchemaController.js +++ b/src/Controllers/SchemaController.js @@ -150,12 +150,15 @@ const defaultColumns: { [string]: SchemaFields } = Object.freeze({ }, }); -const requiredColumns = Object.freeze({ - _User: ['username'], +const requiredColumnsForWrite = Object.freeze({ _Product: ['productIdentifier', 'icon', 'order', 'title', 'subtitle'], _Role: ['name', 'ACL'], }); +const requiredColumnsForRead = Object.freeze({ + _User: ['username'], +}); + const invalidColumns = ['length']; const systemClasses = Object.freeze([ @@ -1270,7 +1273,7 @@ export default class SchemaController { // Validates that all the properties are set for the object validateRequiredColumns(className: string, object: any, query: any) { - const columns = requiredColumns[className]; + const columns = requiredColumnsForWrite[className]; if (!columns || columns.length == 0) { return Promise.resolve(this); } @@ -1601,5 +1604,5 @@ export { convertSchemaToAdapterSchema, VolatileClassesSchemas, SchemaController, - requiredColumns, + requiredColumnsForRead, }; diff --git a/src/RestWrite.js b/src/RestWrite.js index cbe761e9fe..7c894b18d0 100644 --- a/src/RestWrite.js +++ b/src/RestWrite.js @@ -15,7 +15,7 @@ var ClientSDK = require('./ClientSDK'); import RestQuery from './RestQuery'; import _ from 'lodash'; import logger from './logger'; -import { requiredColumns } from './Controllers/SchemaController'; +import { requiredColumnsForRead } from './Controllers/SchemaController'; // query and data are both provided in REST API format. So data // types are encoded by plain old objects. @@ -1670,7 +1670,7 @@ RestWrite.prototype._updateResponseWithData = function (response, data) { 'objectId', 'createdAt', 'updatedAt', - ...(requiredColumns[this.className] || []), + ...(requiredColumnsForRead[this.className] || []), ]; for (const key in response) { if (skipKeys.includes(key)) { From 16c68b9fef5112fb56cda23e6af0478fe87331d1 Mon Sep 17 00:00:00 2001 From: dblythy Date: Thu, 19 May 2022 21:08:19 +1000 Subject: [PATCH 11/12] rename --- spec/CloudCode.spec.js | 4 ++-- src/Controllers/SchemaController.js | 18 ++++++++++-------- src/RestWrite.js | 4 ++-- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/spec/CloudCode.spec.js b/spec/CloudCode.spec.js index b2756c5d31..4df3c90275 100644 --- a/spec/CloudCode.spec.js +++ b/spec/CloudCode.spec.js @@ -1598,7 +1598,7 @@ describe('Cloud Code', () => { expect(obj.get('count')).toBe(0); }); - it('Pointer should not be cleared by triggers', async () => { + it('pointer should not be cleared by triggers', async () => { Parse.Cloud.afterSave('MyObject', () => {}); const foo = await new Parse.Object('Test', { foo: 'bar' }).save(); const obj = await new Parse.Object('MyObject', { foo }).save(); @@ -1606,7 +1606,7 @@ describe('Cloud Code', () => { expect(foo2.get('foo')).toBe('bar'); }); - it('Can set a pointer in triggers', async () => { + it('can set a pointer in triggers', async () => { Parse.Cloud.beforeSave('MyObject', () => {}); Parse.Cloud.afterSave( 'MyObject', diff --git a/src/Controllers/SchemaController.js b/src/Controllers/SchemaController.js index e69a5bb151..11b8aeb8e9 100644 --- a/src/Controllers/SchemaController.js +++ b/src/Controllers/SchemaController.js @@ -150,14 +150,16 @@ const defaultColumns: { [string]: SchemaFields } = Object.freeze({ }, }); -const requiredColumnsForWrite = Object.freeze({ - _Product: ['productIdentifier', 'icon', 'order', 'title', 'subtitle'], - _Role: ['name', 'ACL'], +const requiredColumns = Object.freeze({ + read: { + _User: ['username'], + }, + write: { + _Product: ['productIdentifier', 'icon', 'order', 'title', 'subtitle'], + _Role: ['name', 'ACL'], + } }); -const requiredColumnsForRead = Object.freeze({ - _User: ['username'], -}); const invalidColumns = ['length']; @@ -1273,7 +1275,7 @@ export default class SchemaController { // Validates that all the properties are set for the object validateRequiredColumns(className: string, object: any, query: any) { - const columns = requiredColumnsForWrite[className]; + const columns = requiredColumns.write[className]; if (!columns || columns.length == 0) { return Promise.resolve(this); } @@ -1604,5 +1606,5 @@ export { convertSchemaToAdapterSchema, VolatileClassesSchemas, SchemaController, - requiredColumnsForRead, + requiredColumns, }; diff --git a/src/RestWrite.js b/src/RestWrite.js index 7c894b18d0..0b9bf2a8d4 100644 --- a/src/RestWrite.js +++ b/src/RestWrite.js @@ -15,7 +15,7 @@ var ClientSDK = require('./ClientSDK'); import RestQuery from './RestQuery'; import _ from 'lodash'; import logger from './logger'; -import { requiredColumnsForRead } from './Controllers/SchemaController'; +import { requiredColumns } from './Controllers/SchemaController'; // query and data are both provided in REST API format. So data // types are encoded by plain old objects. @@ -1670,7 +1670,7 @@ RestWrite.prototype._updateResponseWithData = function (response, data) { 'objectId', 'createdAt', 'updatedAt', - ...(requiredColumnsForRead[this.className] || []), + ...(requiredColumns.read[this.className] || []), ]; for (const key in response) { if (skipKeys.includes(key)) { From 55768a7e7585a606db5c4d3f7a61b446a28b49df Mon Sep 17 00:00:00 2001 From: dblythy Date: Fri, 20 May 2022 12:11:12 +1000 Subject: [PATCH 12/12] Update SchemaController.js --- src/Controllers/SchemaController.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Controllers/SchemaController.js b/src/Controllers/SchemaController.js index 11b8aeb8e9..1d857b47fd 100644 --- a/src/Controllers/SchemaController.js +++ b/src/Controllers/SchemaController.js @@ -150,6 +150,7 @@ const defaultColumns: { [string]: SchemaFields } = Object.freeze({ }, }); +// fields required for read or write operations on their respective classes. const requiredColumns = Object.freeze({ read: { _User: ['username'], @@ -160,7 +161,6 @@ const requiredColumns = Object.freeze({ } }); - const invalidColumns = ['length']; const systemClasses = Object.freeze([