From 28b086adc040a5d623e76dba6b986f413427339a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kartal=20Kaan=20Bozdo=C4=9Fan?= Date: Wed, 12 May 2021 16:30:25 +0300 Subject: [PATCH 1/8] Pass deeply nested keys correctly to afterSave triggers Also added a regression test --- spec/ParseAPI.spec.js | 26 ++++++++++++++++++++++++++ src/RestWrite.js | 7 ++++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/spec/ParseAPI.spec.js b/spec/ParseAPI.spec.js index 76143e0580..37f5a792c5 100644 --- a/spec/ParseAPI.spec.js +++ b/spec/ParseAPI.spec.js @@ -691,6 +691,32 @@ describe('miscellaneous', function () { ); }); + it('test afterSave with deeply nested keys (#7384)', async () => { + let triggerTime = 0; + Parse.Cloud.afterSave('GameScore', function (req) { + const object = req.object; + expect(object instanceof Parse.Object).toBeTruthy(); + if (triggerTime == 0) { + // Create + expect(object.get('a')).toEqual({ b: { c: 0 } }); + } else if (triggerTime == 1) { + // Update + expect(object.get('a')).toEqual({ b: { c: 1 } }); + } else { + throw new Error(); + } + triggerTime++; + }); + + const obj = new Parse.Object('GameScore'); + obj.set('a', { b: { c: 0 } }); + await obj.save(); + obj.set('a.b.c', 1); + await obj.save(); + // Make sure the checking has been triggered + expect(triggerTime).toBe(2); + }); + it('test afterSave get original object on update', function (done) { let triggerTime = 0; // Register a mock beforeSave hook diff --git a/src/RestWrite.js b/src/RestWrite.js index c55436983a..c3b2b34be3 100644 --- a/src/RestWrite.js +++ b/src/RestWrite.js @@ -1599,7 +1599,12 @@ RestWrite.prototype.buildUpdatedObject = function (extraData) { if (typeof parentVal !== 'object') { parentVal = {}; } - parentVal[splittedKey[1]] = data[key]; + let curObj = parentVal; + for (let i = 1; i < splittedKey.length - 1; i++) { + if (typeof curObj[splittedKey[i]] === 'undefined') curObj[splittedKey[i]] = {}; + curObj = curObj[splittedKey[i]]; + } + curObj[splittedKey[splittedKey.length - 1]] = data[key]; updatedObject.set(parentProp, parentVal); } delete data[key]; From a63a93a347528e97ae1d132ec73659814f3d1b28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kartal=20Kaan=20Bozdo=C4=9Fan?= Date: Wed, 12 May 2021 16:50:12 +0300 Subject: [PATCH 2/8] Simplified the approach --- src/RestWrite.js | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/src/RestWrite.js b/src/RestWrite.js index c3b2b34be3..ecfa671e8d 100644 --- a/src/RestWrite.js +++ b/src/RestWrite.js @@ -1589,24 +1589,7 @@ RestWrite.prototype.buildUpdatedObject = function (extraData) { const updatedObject = triggers.inflate(extraData, this.originalData); Object.keys(this.data).reduce(function (data, key) { if (key.indexOf('.') > 0) { - if (typeof data[key].__op === 'string') { - updatedObject.set(key, data[key]); - } else { - // subdocument key with dot notation { 'x.y': v } => { 'x': { 'y' : v } }) - const splittedKey = key.split('.'); - const parentProp = splittedKey[0]; - let parentVal = updatedObject.get(parentProp); - if (typeof parentVal !== 'object') { - parentVal = {}; - } - let curObj = parentVal; - for (let i = 1; i < splittedKey.length - 1; i++) { - if (typeof curObj[splittedKey[i]] === 'undefined') curObj[splittedKey[i]] = {}; - curObj = curObj[splittedKey[i]]; - } - curObj[splittedKey[splittedKey.length - 1]] = data[key]; - updatedObject.set(parentProp, parentVal); - } + updatedObject.set(key, data[key]); delete data[key]; } return data; From 46f302c5088fea4e74f11b733ada654590a75aac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kartal=20Kaan=20Bozdo=C4=9Fan?= Date: Wed, 12 May 2021 21:44:27 +0300 Subject: [PATCH 3/8] Added changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b6e132ee3..9558992d66 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -131,6 +131,7 @@ ___ - Add building Docker image as CI check (Manuel Trezza) [#7332](https://github.com/parse-community/parse-server/pull/7332) - Add NPM package-lock version check to CI (Manuel Trezza) [#7333](https://github.com/parse-community/parse-server/pull/7333) - Fix incorrect LiveQuery events triggered for multiple subscriptions on the same class with different events [#7341](https://github.com/parse-community/parse-server/pull/7341) +- Updates on deeply nested documents keys are now correctly passed to afterSave triggers (Kartal Kaan Bozdogan - Ocell) [#7385](https://github.com/parse-community/parse-server/pull/7385) ___ ## 4.5.0 [Full Changelog](https://github.com/parse-community/parse-server/compare/4.4.0...4.5.0) From 1874b22fe85743089c57d6bc28c10e102427be4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kartal=20Kaan=20Bozdo=C4=9Fan?= Date: Thu, 13 May 2021 14:17:27 +0300 Subject: [PATCH 4/8] Expanded the regression test --- spec/ParseAPI.spec.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/spec/ParseAPI.spec.js b/spec/ParseAPI.spec.js index 37f5a792c5..676c5d77ef 100644 --- a/spec/ParseAPI.spec.js +++ b/spec/ParseAPI.spec.js @@ -698,10 +698,10 @@ describe('miscellaneous', function () { expect(object instanceof Parse.Object).toBeTruthy(); if (triggerTime == 0) { // Create - expect(object.get('a')).toEqual({ b: { c: 0 } }); + expect(object.get('a')).toEqual({ b: { c: 0 }, d: 0 }); } else if (triggerTime == 1) { // Update - expect(object.get('a')).toEqual({ b: { c: 1 } }); + expect(object.get('a')).toEqual({ b: { c: 1 }, d: 2 }); } else { throw new Error(); } @@ -709,9 +709,10 @@ describe('miscellaneous', function () { }); const obj = new Parse.Object('GameScore'); - obj.set('a', { b: { c: 0 } }); + obj.set('a', { b: { c: 0 }, d: 0 }); await obj.save(); obj.set('a.b.c', 1); + obj.set('a.d', 2); await obj.save(); // Make sure the checking has been triggered expect(triggerTime).toBe(2); From 49e7974172fd4d2a0f81f839cc8da6e54fe7f84a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kartal=20Kaan=20Bozdo=C4=9Fan?= Date: Thu, 13 May 2021 19:48:51 +0300 Subject: [PATCH 5/8] Use a patched version of the js sdk Solves problems with nested keys Expanded the regression test --- package-lock.json | 38 +++++++++++++++++++++++------------- package.json | 2 +- spec/ParseAPI.spec.js | 45 +++++++++++++++++++++++++++++++++---------- 3 files changed, 61 insertions(+), 24 deletions(-) diff --git a/package-lock.json b/package-lock.json index 337e95eea5..11ea98da61 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6892,9 +6892,9 @@ } }, "idb-keyval": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/idb-keyval/-/idb-keyval-5.0.4.tgz", - "integrity": "sha512-qS0kplHuadZujoE90ze0NUkhW0/Fbfib7d+mYNMXNEn45NSh2NWY3fBewoX4GZUsKkGHBgc8JiAwMx0zrfL3LQ==" + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/idb-keyval/-/idb-keyval-5.0.5.tgz", + "integrity": "sha512-cqi65rrjhgPExI9vmSU7VcYEbHCUfIBY+9YUWxyr0PyGizptFgGFnvZQ0w+tqOXk1lUcGCZGVLfabf7QnR2S0g==" }, "ieee754": { "version": "1.2.1", @@ -9981,24 +9981,31 @@ } }, "parse": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/parse/-/parse-3.2.0.tgz", - "integrity": "sha512-yncA9l0LneOzzBFACVjdPNpWuNGQ/LhXGEO/qj6vYAyJpwWabNf7Eq2ucMAWzALb13KDRoAiTlb2cH3Nt/S9gw==", + "version": "github:Ocell-io/Parse-SDK-JS#293011e0b537849b13d7488543f96c7c79f9b44f", + "from": "github:Ocell-io/Parse-SDK-JS#293011e0b537849b13d7488543f96c7c79f9b44f", "requires": { - "@babel/runtime": "7.13.10", - "@babel/runtime-corejs3": "7.13.10", + "@babel/runtime": "7.13.17", + "@babel/runtime-corejs3": "7.13.17", "crypto-js": "4.0.0", - "idb-keyval": "5.0.4", + "idb-keyval": "5.0.5", "react-native-crypto-js": "1.0.0", "uuid": "3.4.0", - "ws": "7.4.4", + "ws": "7.4.5", "xmlhttprequest": "1.8.0" }, "dependencies": { + "@babel/runtime": { + "version": "7.13.17", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.17.tgz", + "integrity": "sha512-NCdgJEelPTSh+FEFylhnP1ylq848l1z9t9N0j1Lfbcw0+KXGjsTvUmkxy+voLLXB5SOKMbLLx4jxYliGrYQseA==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, "@babel/runtime-corejs3": { - "version": "7.13.10", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.13.10.tgz", - "integrity": "sha512-x/XYVQ1h684pp1mJwOV4CyvqZXqbc8CMsMGUnAbuc82ZCdv1U63w5RSUzgDSXQHG5Rps/kiksH6g2D5BuaKyXg==", + "version": "7.13.17", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.13.17.tgz", + "integrity": "sha512-RGXINY1YvduBlGrP+vHjJqd/nK7JVpfM4rmZLGMx77WoL3sMrhheA0qxii9VNn1VHnxJLEyxmvCB+Wqc+x/FMw==", "requires": { "core-js-pure": "^3.0.0", "regenerator-runtime": "^0.13.4" @@ -10008,6 +10015,11 @@ "version": "3.4.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" + }, + "ws": { + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.5.tgz", + "integrity": "sha512-xzyu3hFvomRfXKH8vOFMU3OguG6oOvhXMo3xsGy3xWExqaM2dxBbVxuD99O7m3ZUFMvvscsZDqxfgMaRr/Nr1g==" } } }, diff --git a/package.json b/package.json index f90933e2ca..41f6151f2a 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "mime": "2.5.2", "mongodb": "3.6.6", "mustache": "4.2.0", - "parse": "3.2.0", + "parse": "github:Ocell-io/Parse-SDK-JS#293011e0b537849b13d7488543f96c7c79f9b44f", "pg-monitor": "1.4.1", "pg-promise": "10.10.1", "pluralize": "8.0.0", diff --git a/spec/ParseAPI.spec.js b/spec/ParseAPI.spec.js index 676c5d77ef..1bcb1d1ac7 100644 --- a/spec/ParseAPI.spec.js +++ b/spec/ParseAPI.spec.js @@ -696,26 +696,51 @@ describe('miscellaneous', function () { Parse.Cloud.afterSave('GameScore', function (req) { const object = req.object; expect(object instanceof Parse.Object).toBeTruthy(); - if (triggerTime == 0) { + triggerTime++; + if (triggerTime == 1) { // Create - expect(object.get('a')).toEqual({ b: { c: 0 }, d: 0 }); - } else if (triggerTime == 1) { - // Update - expect(object.get('a')).toEqual({ b: { c: 1 }, d: 2 }); + expect(object.get('a')).toEqual({ b: 0, c: { d: 1 } }); + expect(object.get('e')).toEqual(2); + } else if (triggerTime == 2) { + // Update, increment + expect(object.get('a')).toEqual({ b: 10, c: { d: 12 } }); + expect(object.get('e')).toEqual(14); + } else if (triggerTime == 3) { + // Update, set + expect(object.get('a')).toEqual({ b: 100, c: { d: 200 } }); + expect(object.get('e')).toEqual(300); + } else if (triggerTime == 4) { + // Update, unset on a.c.d + console.log(JSON.serialize(object.get('a'))); + expect(object.get('a')).toEqual({ b: 100, c: {} }); + expect(object.get('e')).toEqual(300); + } else if (triggerTime == 5) { + // Update, unset on a.b + expect(object.get('a')).toEqual({ c: {} }); + expect(object.get('e')).toEqual(300); } else { throw new Error(); } - triggerTime++; }); const obj = new Parse.Object('GameScore'); - obj.set('a', { b: { c: 0 }, d: 0 }); + obj.set('a', { b: 0, c: { d: 1 } }); + obj.set('e', 2); + await obj.save(); + obj.increment('a.b', 10); + obj.increment('a.c.d', 11); + obj.increment('e', 12); + await obj.save(); + obj.set('a.b', 100); + obj.set('a.c.d', 200); + obj.set('e', 300); + await obj.save(); + obj.unset('a.c.d'); await obj.save(); - obj.set('a.b.c', 1); - obj.set('a.d', 2); + obj.unset('a.b'); await obj.save(); // Make sure the checking has been triggered - expect(triggerTime).toBe(2); + expect(triggerTime).toBe(5); }); it('test afterSave get original object on update', function (done) { From 496e31cfe4b9ed4e397095491df0ab2d9424b0cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kartal=20Kaan=20Bozdo=C4=9Fan?= Date: Thu, 13 May 2021 20:37:09 +0300 Subject: [PATCH 6/8] Modified a test ParseUser.get('password') used to be undefined. Now, password is not an attribute --- spec/ParseUser.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/ParseUser.spec.js b/spec/ParseUser.spec.js index 91aeb4920a..f5e191799a 100644 --- a/spec/ParseUser.spec.js +++ b/spec/ParseUser.spec.js @@ -793,7 +793,7 @@ describe('Parse.User testing', () => { user.set('username', 'test'); await user.save(); - equal(Object.keys(user.attributes).length, 6); + equal(Object.keys(user.attributes).length, 5); ok(user.attributes['username']); ok(user.attributes['email']); await user.destroy(); From 64d3f0f70d7119b0ce62ba357dfbf34f33428908 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kartal=20Kaan=20Bozdo=C4=9Fan?= Date: Thu, 13 May 2021 22:19:20 +0300 Subject: [PATCH 7/8] Updated the sdk fork --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index c4c682adc4..4bab03734e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10040,8 +10040,8 @@ } }, "parse": { - "version": "github:Ocell-io/Parse-SDK-JS#293011e0b537849b13d7488543f96c7c79f9b44f", - "from": "github:Ocell-io/Parse-SDK-JS#293011e0b537849b13d7488543f96c7c79f9b44f", + "version": "github:Ocell-io/Parse-SDK-JS#1ed03b3dca8af9c648606f95a6cb473b36b33a04", + "from": "github:Ocell-io/Parse-SDK-JS#1ed03b3dca8af9c648606f95a6cb473b36b33a04", "requires": { "@babel/runtime": "7.13.17", "@babel/runtime-corejs3": "7.13.17", diff --git a/package.json b/package.json index ae2b50a7cb..01b4d145e3 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "mime": "2.5.2", "mongodb": "3.6.6", "mustache": "4.2.0", - "parse": "github:Ocell-io/Parse-SDK-JS#293011e0b537849b13d7488543f96c7c79f9b44f", + "parse": "github:Ocell-io/Parse-SDK-JS#1ed03b3dca8af9c648606f95a6cb473b36b33a04", "pg-monitor": "1.4.1", "pg-promise": "10.10.1", "pluralize": "8.0.0", From 85a9a942c3ce28dd41c2226bb243be0d9ed16d3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kartal=20Kaan=20Bozdo=C4=9Fan?= Date: Fri, 14 May 2021 13:47:00 +0300 Subject: [PATCH 8/8] Remove console.log --- spec/ParseAPI.spec.js | 1 - 1 file changed, 1 deletion(-) diff --git a/spec/ParseAPI.spec.js b/spec/ParseAPI.spec.js index 1bcb1d1ac7..87306eefd2 100644 --- a/spec/ParseAPI.spec.js +++ b/spec/ParseAPI.spec.js @@ -711,7 +711,6 @@ describe('miscellaneous', function () { expect(object.get('e')).toEqual(300); } else if (triggerTime == 4) { // Update, unset on a.c.d - console.log(JSON.serialize(object.get('a'))); expect(object.get('a')).toEqual({ b: 100, c: {} }); expect(object.get('e')).toEqual(300); } else if (triggerTime == 5) {