GraphQL: Optimize queries, fixes some null returns (on object), fix stitched GraphQLUpload (#6709)
* Optimize query, fixes some null returns, fix stitched GraphQLUpload * Fix authData key selection * Prefer Iso string since other GraphQL solutions use this format * fix tests Co-authored-by: Antonio Davi Macedo Coelho de Castro <adavimacedo@gmail.com>
This commit is contained in:
195
package-lock.json
generated
195
package-lock.json
generated
@@ -4,6 +4,63 @@
|
|||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@apollo/client": {
|
||||||
|
"version": "3.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@apollo/client/-/client-3.2.1.tgz",
|
||||||
|
"integrity": "sha512-w1EdCf3lvSwsxG2zbn8Rm31nPh9gQrB7u61BnU1QCM5BNIfOxiuuldzGNMHi5kI9KleisFvZl/9OA7pEkVg/yw==",
|
||||||
|
"requires": {
|
||||||
|
"@graphql-typed-document-node/core": "^3.0.0",
|
||||||
|
"@types/zen-observable": "^0.8.0",
|
||||||
|
"@wry/context": "^0.5.2",
|
||||||
|
"@wry/equality": "^0.2.0",
|
||||||
|
"fast-json-stable-stringify": "^2.0.0",
|
||||||
|
"graphql-tag": "^2.11.0",
|
||||||
|
"hoist-non-react-statics": "^3.3.2",
|
||||||
|
"optimism": "^0.12.1",
|
||||||
|
"prop-types": "^15.7.2",
|
||||||
|
"symbol-observable": "^2.0.0",
|
||||||
|
"terser": "^5.2.0",
|
||||||
|
"ts-invariant": "^0.4.4",
|
||||||
|
"tslib": "^1.10.0",
|
||||||
|
"zen-observable": "^0.8.14"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@wry/context": {
|
||||||
|
"version": "0.5.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@wry/context/-/context-0.5.2.tgz",
|
||||||
|
"integrity": "sha512-B/JLuRZ/vbEKHRUiGj6xiMojST1kHhu4WcreLfNN7q9DqQFrb97cWgf/kiYsPSUCAMVN0HzfFc8XjJdzgZzfjw==",
|
||||||
|
"requires": {
|
||||||
|
"tslib": "^1.9.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@wry/equality": {
|
||||||
|
"version": "0.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@wry/equality/-/equality-0.2.0.tgz",
|
||||||
|
"integrity": "sha512-Y4d+WH6hs+KZJUC8YKLYGarjGekBrhslDbf/R20oV+AakHPINSitHfDRQz3EGcEWc1luXYNUvMhawWtZVWNGvQ==",
|
||||||
|
"requires": {
|
||||||
|
"tslib": "^1.9.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"graphql-tag": {
|
||||||
|
"version": "2.11.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.11.0.tgz",
|
||||||
|
"integrity": "sha512-VmsD5pJqWJnQZMUeRwrDhfgoyqcfwEkvtpANqcoUG8/tOLkwNgU9mzub/Mc78OJMhHjx7gfAMTxzdG43VGg3bA=="
|
||||||
|
},
|
||||||
|
"optimism": {
|
||||||
|
"version": "0.12.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/optimism/-/optimism-0.12.2.tgz",
|
||||||
|
"integrity": "sha512-k7hFhlmfLl6HNThIuuvYMQodC1c+q6Uc6V9cLVsMWyW514QuaxVJH/khPu2vLRIoDTpFdJ5sojlARhg1rzyGbg==",
|
||||||
|
"requires": {
|
||||||
|
"@wry/context": "^0.5.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"symbol-observable": {
|
||||||
|
"version": "2.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-2.0.3.tgz",
|
||||||
|
"integrity": "sha512-sQV7phh2WCYAn81oAkakC5qjq2Ml0g8ozqz03wOGnx9dDlG1de6yrF+0RAzSJD8fPUow3PTSMf2SAbOGxb93BA=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"@apollo/protobufjs": {
|
"@apollo/protobufjs": {
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/@apollo/protobufjs/-/protobufjs-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/@apollo/protobufjs/-/protobufjs-1.0.5.tgz",
|
||||||
@@ -2794,6 +2851,65 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@graphql-tools/links": {
|
||||||
|
"version": "6.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@graphql-tools/links/-/links-6.2.4.tgz",
|
||||||
|
"integrity": "sha512-dQH3oWVTkCwzGmfIi1OjyKAjPw1jOexP1f3hv8UajgU7Um/DCjVkvXQHeMGlihXg4bH/wogFheCJ0SwF4oFFUA==",
|
||||||
|
"requires": {
|
||||||
|
"@graphql-tools/utils": "^6.2.4",
|
||||||
|
"apollo-link": "1.2.14",
|
||||||
|
"apollo-upload-client": "14.1.2",
|
||||||
|
"cross-fetch": "3.0.6",
|
||||||
|
"form-data": "3.0.0",
|
||||||
|
"is-promise": "4.0.0",
|
||||||
|
"tslib": "~2.0.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@ardatan/aggregate-error": {
|
||||||
|
"version": "0.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ardatan/aggregate-error/-/aggregate-error-0.0.6.tgz",
|
||||||
|
"integrity": "sha512-vyrkEHG1jrukmzTPtyWB4NLPauUw5bQeg4uhn8f+1SSynmrOcyvlb1GKQjjgoBzElLdfXCRYX8UnBlhklOHYRQ==",
|
||||||
|
"requires": {
|
||||||
|
"tslib": "~2.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@graphql-tools/utils": {
|
||||||
|
"version": "6.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-6.2.4.tgz",
|
||||||
|
"integrity": "sha512-ybgZ9EIJE3JMOtTrTd2VcIpTXtDrn2q6eiYkeYMKRVh3K41+LZa6YnR2zKERTXqTWqhobROwLt4BZbw2O3Aeeg==",
|
||||||
|
"requires": {
|
||||||
|
"@ardatan/aggregate-error": "0.0.6",
|
||||||
|
"camel-case": "4.1.1",
|
||||||
|
"tslib": "~2.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"apollo-upload-client": {
|
||||||
|
"version": "14.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/apollo-upload-client/-/apollo-upload-client-14.1.2.tgz",
|
||||||
|
"integrity": "sha512-ozaW+4tnVz1rpfwiQwG3RCdCcZ93RV/37ZQbRnObcQ9mjb+zur58sGDPVg9Ef3fiujLmiE/Fe9kdgvIMA3VOjA==",
|
||||||
|
"requires": {
|
||||||
|
"@apollo/client": "^3.1.5",
|
||||||
|
"@babel/runtime": "^7.11.2",
|
||||||
|
"extract-files": "^9.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"extract-files": {
|
||||||
|
"version": "9.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/extract-files/-/extract-files-9.0.0.tgz",
|
||||||
|
"integrity": "sha512-CvdFfHkC95B4bBBk36hcEmvdR2awOdhhVUYH6S/zrVj3477zven/fJMYg7121h4T1xHZC+tetUpubpAhxwI7hQ=="
|
||||||
|
},
|
||||||
|
"is-promise": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="
|
||||||
|
},
|
||||||
|
"tslib": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"@graphql-tools/merge": {
|
"@graphql-tools/merge": {
|
||||||
"version": "6.2.0",
|
"version": "6.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@graphql-tools/merge/-/merge-6.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@graphql-tools/merge/-/merge-6.2.0.tgz",
|
||||||
@@ -2949,6 +3065,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@graphql-typed-document-node/core": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-wYn6r8zVZyQJ6rQaALBEln5B1pzxb9shV5Ef97kTvn6yVGrqyXVnDqnU24MXnFubR+rZjBY9NWuxX3FB2sTsjg=="
|
||||||
|
},
|
||||||
"@istanbuljs/load-nyc-config": {
|
"@istanbuljs/load-nyc-config": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz",
|
||||||
@@ -3434,8 +3555,7 @@
|
|||||||
"@types/zen-observable": {
|
"@types/zen-observable": {
|
||||||
"version": "0.8.0",
|
"version": "0.8.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/zen-observable/-/zen-observable-0.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/zen-observable/-/zen-observable-0.8.0.tgz",
|
||||||
"integrity": "sha512-te5lMAWii1uEJ4FwLjzdlbw3+n0FZNOvFXHxQDKeT0dilh7HOzdMzV2TrJVUzq8ep7J4Na8OUYPRLSQkJHAlrg==",
|
"integrity": "sha512-te5lMAWii1uEJ4FwLjzdlbw3+n0FZNOvFXHxQDKeT0dilh7HOzdMzV2TrJVUzq8ep7J4Na8OUYPRLSQkJHAlrg=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"@wry/context": {
|
"@wry/context": {
|
||||||
"version": "0.4.4",
|
"version": "0.4.4",
|
||||||
@@ -4492,6 +4612,11 @@
|
|||||||
"integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw=",
|
"integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"buffer-from": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A=="
|
||||||
|
},
|
||||||
"buffer-writer": {
|
"buffer-writer": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz",
|
||||||
@@ -5124,6 +5249,14 @@
|
|||||||
"cross-spawn": "^7.0.1"
|
"cross-spawn": "^7.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"cross-fetch": {
|
||||||
|
"version": "3.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.0.6.tgz",
|
||||||
|
"integrity": "sha512-KBPUbqgFjzWlVcURG+Svp9TlhA5uliYtiNx/0r8nv0pdypeQCRJ9IaSIc3q/x3q8t3F75cHuwxVql1HFGHCNJQ==",
|
||||||
|
"requires": {
|
||||||
|
"node-fetch": "2.6.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"cross-spawn": {
|
"cross-spawn": {
|
||||||
"version": "7.0.1",
|
"version": "7.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.1.tgz",
|
||||||
@@ -7253,6 +7386,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"hoist-non-react-statics": {
|
||||||
|
"version": "3.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
|
||||||
|
"integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
|
||||||
|
"requires": {
|
||||||
|
"react-is": "^16.7.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"html-escaper": {
|
"html-escaper": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
|
||||||
@@ -8089,8 +8230,7 @@
|
|||||||
"js-tokens": {
|
"js-tokens": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"js-yaml": {
|
"js-yaml": {
|
||||||
"version": "3.13.1",
|
"version": "3.13.1",
|
||||||
@@ -8924,7 +9064,6 @@
|
|||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
||||||
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
|
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"js-tokens": "^3.0.0 || ^4.0.0"
|
"js-tokens": "^3.0.0 || ^4.0.0"
|
||||||
}
|
}
|
||||||
@@ -10522,6 +10661,16 @@
|
|||||||
"with-callback": "^1.0.2"
|
"with-callback": "^1.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"prop-types": {
|
||||||
|
"version": "15.7.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
|
||||||
|
"integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==",
|
||||||
|
"requires": {
|
||||||
|
"loose-envify": "^1.4.0",
|
||||||
|
"object-assign": "^4.1.1",
|
||||||
|
"react-is": "^16.8.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"proto-list": {
|
"proto-list": {
|
||||||
"version": "1.2.4",
|
"version": "1.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz",
|
||||||
@@ -10628,6 +10777,11 @@
|
|||||||
"unpipe": "1.0.0"
|
"unpipe": "1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"react-is": {
|
||||||
|
"version": "16.13.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||||
|
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
||||||
|
},
|
||||||
"react-native-crypto-js": {
|
"react-native-crypto-js": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-native-crypto-js/-/react-native-crypto-js-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-native-crypto-js/-/react-native-crypto-js-1.0.0.tgz",
|
||||||
@@ -11413,6 +11567,15 @@
|
|||||||
"urix": "^0.1.0"
|
"urix": "^0.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"source-map-support": {
|
||||||
|
"version": "0.5.19",
|
||||||
|
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz",
|
||||||
|
"integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==",
|
||||||
|
"requires": {
|
||||||
|
"buffer-from": "^1.0.0",
|
||||||
|
"source-map": "^0.6.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"source-map-url": {
|
"source-map-url": {
|
||||||
"version": "0.4.0",
|
"version": "0.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz",
|
||||||
@@ -11774,6 +11937,28 @@
|
|||||||
"xtend": "^4.0.0"
|
"xtend": "^4.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"terser": {
|
||||||
|
"version": "5.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/terser/-/terser-5.3.3.tgz",
|
||||||
|
"integrity": "sha512-vRQDIlD+2Pg8YMwVK9kMM3yGylG95EIwzBai1Bw7Ot4OBfn3VP1TZn3EWx4ep2jERN/AmnVaTiGuelZSN7ds/A==",
|
||||||
|
"requires": {
|
||||||
|
"commander": "^2.20.0",
|
||||||
|
"source-map": "~0.7.2",
|
||||||
|
"source-map-support": "~0.5.19"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"commander": {
|
||||||
|
"version": "2.20.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
|
||||||
|
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
|
||||||
|
},
|
||||||
|
"source-map": {
|
||||||
|
"version": "0.7.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
|
||||||
|
"integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"test-exclude": {
|
"test-exclude": {
|
||||||
"version": "6.0.0",
|
"version": "6.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz",
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
"license": "BSD-3-Clause",
|
"license": "BSD-3-Clause",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@apollographql/graphql-playground-html": "1.6.26",
|
"@apollographql/graphql-playground-html": "1.6.26",
|
||||||
|
"@graphql-tools/links": "^6.2.4",
|
||||||
"@graphql-tools/stitch": "6.2.0",
|
"@graphql-tools/stitch": "6.2.0",
|
||||||
"@graphql-tools/utils": "6.2.1",
|
"@graphql-tools/utils": "6.2.1",
|
||||||
"@parse/fs-files-adapter": "1.0.1",
|
"@parse/fs-files-adapter": "1.0.1",
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ const InMemoryCacheAdapter = require('../lib/Adapters/Cache/InMemoryCacheAdapter
|
|||||||
.InMemoryCacheAdapter;
|
.InMemoryCacheAdapter;
|
||||||
|
|
||||||
const mockAdapter = {
|
const mockAdapter = {
|
||||||
createFile: async (filename) => ({
|
createFile: async filename => ({
|
||||||
name: filename,
|
name: filename,
|
||||||
location: `http://www.somewhere.com/${filename}`,
|
location: `http://www.somewhere.com/${filename}`,
|
||||||
}),
|
}),
|
||||||
@@ -2624,14 +2624,17 @@ describe('beforeLogin hook', () => {
|
|||||||
const createFileSpy = spyOn(mockAdapter, 'createFile').and.callThrough();
|
const createFileSpy = spyOn(mockAdapter, 'createFile').and.callThrough();
|
||||||
Parse.Cloud.beforeSaveFile(() => {
|
Parse.Cloud.beforeSaveFile(() => {
|
||||||
const newFile = new Parse.File('some-file.txt');
|
const newFile = new Parse.File('some-file.txt');
|
||||||
newFile._url = 'http://www.somewhere.com/parse/files/some-app-id/some-file.txt';
|
newFile._url =
|
||||||
|
'http://www.somewhere.com/parse/files/some-app-id/some-file.txt';
|
||||||
return newFile;
|
return newFile;
|
||||||
});
|
});
|
||||||
const file = new Parse.File('popeye.txt', [1, 2, 3], 'text/plain');
|
const file = new Parse.File('popeye.txt', [1, 2, 3], 'text/plain');
|
||||||
const result = await file.save({ useMasterKey: true });
|
const result = await file.save({ useMasterKey: true });
|
||||||
expect(result).toBe(file);
|
expect(result).toBe(file);
|
||||||
expect(result._name).toBe('some-file.txt');
|
expect(result._name).toBe('some-file.txt');
|
||||||
expect(result._url).toBe('http://www.somewhere.com/parse/files/some-app-id/some-file.txt');
|
expect(result._url).toBe(
|
||||||
|
'http://www.somewhere.com/parse/files/some-app-id/some-file.txt'
|
||||||
|
);
|
||||||
expect(createFileSpy).not.toHaveBeenCalled();
|
expect(createFileSpy).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -2651,7 +2654,7 @@ describe('beforeLogin hook', () => {
|
|||||||
it('beforeSaveFile should change values of uploaded file by editing fileObject directly', async () => {
|
it('beforeSaveFile should change values of uploaded file by editing fileObject directly', async () => {
|
||||||
await reconfigureServer({ filesAdapter: mockAdapter });
|
await reconfigureServer({ filesAdapter: mockAdapter });
|
||||||
const createFileSpy = spyOn(mockAdapter, 'createFile').and.callThrough();
|
const createFileSpy = spyOn(mockAdapter, 'createFile').and.callThrough();
|
||||||
Parse.Cloud.beforeSaveFile(async (req) => {
|
Parse.Cloud.beforeSaveFile(async req => {
|
||||||
expect(req.triggerName).toEqual('beforeSaveFile');
|
expect(req.triggerName).toEqual('beforeSaveFile');
|
||||||
expect(req.master).toBe(true);
|
expect(req.master).toBe(true);
|
||||||
req.file.addMetadata('foo', 'bar');
|
req.file.addMetadata('foo', 'bar');
|
||||||
@@ -2669,16 +2672,25 @@ describe('beforeLogin hook', () => {
|
|||||||
foo: 'bar',
|
foo: 'bar',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
expect(createFileSpy).toHaveBeenCalledWith(jasmine.any(String), newData, 'text/plain', newOptions);
|
expect(createFileSpy).toHaveBeenCalledWith(
|
||||||
|
jasmine.any(String),
|
||||||
|
newData,
|
||||||
|
'text/plain',
|
||||||
|
newOptions
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('beforeSaveFile should change values by returning new fileObject', async () => {
|
it('beforeSaveFile should change values by returning new fileObject', async () => {
|
||||||
await reconfigureServer({ filesAdapter: mockAdapter });
|
await reconfigureServer({ filesAdapter: mockAdapter });
|
||||||
const createFileSpy = spyOn(mockAdapter, 'createFile').and.callThrough();
|
const createFileSpy = spyOn(mockAdapter, 'createFile').and.callThrough();
|
||||||
Parse.Cloud.beforeSaveFile(async (req) => {
|
Parse.Cloud.beforeSaveFile(async req => {
|
||||||
expect(req.triggerName).toEqual('beforeSaveFile');
|
expect(req.triggerName).toEqual('beforeSaveFile');
|
||||||
expect(req.fileSize).toBe(3);
|
expect(req.fileSize).toBe(3);
|
||||||
const newFile = new Parse.File('donald_duck.pdf', [4, 5, 6], 'application/pdf');
|
const newFile = new Parse.File(
|
||||||
|
'donald_duck.pdf',
|
||||||
|
[4, 5, 6],
|
||||||
|
'application/pdf'
|
||||||
|
);
|
||||||
newFile.setMetadata({ foo: 'bar' });
|
newFile.setMetadata({ foo: 'bar' });
|
||||||
newFile.setTags({ tagA: 'some-tag' });
|
newFile.setTags({ tagA: 'some-tag' });
|
||||||
return newFile;
|
return newFile;
|
||||||
@@ -2696,15 +2708,22 @@ describe('beforeLogin hook', () => {
|
|||||||
foo: 'bar',
|
foo: 'bar',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
expect(createFileSpy).toHaveBeenCalledWith(jasmine.any(String), newData, newContentType, newOptions);
|
expect(createFileSpy).toHaveBeenCalledWith(
|
||||||
|
jasmine.any(String),
|
||||||
|
newData,
|
||||||
|
newContentType,
|
||||||
|
newOptions
|
||||||
|
);
|
||||||
const expectedFileName = 'donald_duck.pdf';
|
const expectedFileName = 'donald_duck.pdf';
|
||||||
expect(file._name.indexOf(expectedFileName)).toBe(file._name.length - expectedFileName.length);
|
expect(file._name.indexOf(expectedFileName)).toBe(
|
||||||
|
file._name.length - expectedFileName.length
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('beforeSaveFile should contain metadata and tags saved from client', async () => {
|
it('beforeSaveFile should contain metadata and tags saved from client', async () => {
|
||||||
await reconfigureServer({ filesAdapter: mockAdapter });
|
await reconfigureServer({ filesAdapter: mockAdapter });
|
||||||
const createFileSpy = spyOn(mockAdapter, 'createFile').and.callThrough();
|
const createFileSpy = spyOn(mockAdapter, 'createFile').and.callThrough();
|
||||||
Parse.Cloud.beforeSaveFile(async (req) => {
|
Parse.Cloud.beforeSaveFile(async req => {
|
||||||
expect(req.triggerName).toEqual('beforeSaveFile');
|
expect(req.triggerName).toEqual('beforeSaveFile');
|
||||||
expect(req.fileSize).toBe(3);
|
expect(req.fileSize).toBe(3);
|
||||||
expect(req.file).toBeInstanceOf(Parse.File);
|
expect(req.file).toBeInstanceOf(Parse.File);
|
||||||
@@ -2721,7 +2740,12 @@ describe('beforeLogin hook', () => {
|
|||||||
metadata: { foo: 'bar' },
|
metadata: { foo: 'bar' },
|
||||||
tags: { bar: 'foo' },
|
tags: { bar: 'foo' },
|
||||||
};
|
};
|
||||||
expect(createFileSpy).toHaveBeenCalledWith(jasmine.any(String), jasmine.any(Buffer), 'text/plain', options);
|
expect(createFileSpy).toHaveBeenCalledWith(
|
||||||
|
jasmine.any(String),
|
||||||
|
jasmine.any(Buffer),
|
||||||
|
'text/plain',
|
||||||
|
options
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('beforeSaveFile should return same file data with new file name', async () => {
|
it('beforeSaveFile should return same file data with new file name', async () => {
|
||||||
@@ -2742,20 +2766,23 @@ describe('beforeLogin hook', () => {
|
|||||||
it('afterSaveFile should set fileSize to null if beforeSave returns an already saved file', async () => {
|
it('afterSaveFile should set fileSize to null if beforeSave returns an already saved file', async () => {
|
||||||
await reconfigureServer({ filesAdapter: mockAdapter });
|
await reconfigureServer({ filesAdapter: mockAdapter });
|
||||||
const createFileSpy = spyOn(mockAdapter, 'createFile').and.callThrough();
|
const createFileSpy = spyOn(mockAdapter, 'createFile').and.callThrough();
|
||||||
Parse.Cloud.beforeSaveFile((req) => {
|
Parse.Cloud.beforeSaveFile(req => {
|
||||||
expect(req.fileSize).toBe(3);
|
expect(req.fileSize).toBe(3);
|
||||||
const newFile = new Parse.File('some-file.txt');
|
const newFile = new Parse.File('some-file.txt');
|
||||||
newFile._url = 'http://www.somewhere.com/parse/files/some-app-id/some-file.txt';
|
newFile._url =
|
||||||
|
'http://www.somewhere.com/parse/files/some-app-id/some-file.txt';
|
||||||
return newFile;
|
return newFile;
|
||||||
});
|
});
|
||||||
Parse.Cloud.afterSaveFile((req) => {
|
Parse.Cloud.afterSaveFile(req => {
|
||||||
expect(req.fileSize).toBe(null);
|
expect(req.fileSize).toBe(null);
|
||||||
});
|
});
|
||||||
const file = new Parse.File('popeye.txt', [1, 2, 3], 'text/plain');
|
const file = new Parse.File('popeye.txt', [1, 2, 3], 'text/plain');
|
||||||
const result = await file.save({ useMasterKey: true });
|
const result = await file.save({ useMasterKey: true });
|
||||||
expect(result).toBe(result);
|
expect(result).toBe(result);
|
||||||
expect(result._name).toBe('some-file.txt');
|
expect(result._name).toBe('some-file.txt');
|
||||||
expect(result._url).toBe('http://www.somewhere.com/parse/files/some-app-id/some-file.txt');
|
expect(result._url).toBe(
|
||||||
|
'http://www.somewhere.com/parse/files/some-app-id/some-file.txt'
|
||||||
|
);
|
||||||
expect(createFileSpy).not.toHaveBeenCalled();
|
expect(createFileSpy).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -2773,13 +2800,13 @@ describe('beforeLogin hook', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('afterSaveFile should call with fileObject', async (done) => {
|
it('afterSaveFile should call with fileObject', async done => {
|
||||||
await reconfigureServer({ filesAdapter: mockAdapter });
|
await reconfigureServer({ filesAdapter: mockAdapter });
|
||||||
Parse.Cloud.beforeSaveFile(async (req) => {
|
Parse.Cloud.beforeSaveFile(async req => {
|
||||||
req.file.setTags({ tagA: 'some-tag' });
|
req.file.setTags({ tagA: 'some-tag' });
|
||||||
req.file.setMetadata({ foo: 'bar' });
|
req.file.setMetadata({ foo: 'bar' });
|
||||||
});
|
});
|
||||||
Parse.Cloud.afterSaveFile(async (req) => {
|
Parse.Cloud.afterSaveFile(async req => {
|
||||||
expect(req.master).toBe(true);
|
expect(req.master).toBe(true);
|
||||||
expect(req.file._tags).toEqual({ tagA: 'some-tag' });
|
expect(req.file._tags).toEqual({ tagA: 'some-tag' });
|
||||||
expect(req.file._metadata).toEqual({ foo: 'bar' });
|
expect(req.file._metadata).toEqual({ foo: 'bar' });
|
||||||
@@ -2789,15 +2816,19 @@ describe('beforeLogin hook', () => {
|
|||||||
await file.save({ useMasterKey: true });
|
await file.save({ useMasterKey: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('afterSaveFile should change fileSize when file data changes', async (done) => {
|
it('afterSaveFile should change fileSize when file data changes', async done => {
|
||||||
await reconfigureServer({ filesAdapter: mockAdapter });
|
await reconfigureServer({ filesAdapter: mockAdapter });
|
||||||
Parse.Cloud.beforeSaveFile(async (req) => {
|
Parse.Cloud.beforeSaveFile(async req => {
|
||||||
expect(req.fileSize).toBe(3);
|
expect(req.fileSize).toBe(3);
|
||||||
expect(req.master).toBe(true);
|
expect(req.master).toBe(true);
|
||||||
const newFile = new Parse.File('donald_duck.pdf', [4, 5, 6, 7, 8, 9], 'application/pdf');
|
const newFile = new Parse.File(
|
||||||
|
'donald_duck.pdf',
|
||||||
|
[4, 5, 6, 7, 8, 9],
|
||||||
|
'application/pdf'
|
||||||
|
);
|
||||||
return newFile;
|
return newFile;
|
||||||
});
|
});
|
||||||
Parse.Cloud.afterSaveFile(async (req) => {
|
Parse.Cloud.afterSaveFile(async req => {
|
||||||
expect(req.fileSize).toBe(6);
|
expect(req.fileSize).toBe(6);
|
||||||
expect(req.master).toBe(true);
|
expect(req.master).toBe(true);
|
||||||
done();
|
done();
|
||||||
@@ -2808,7 +2839,7 @@ describe('beforeLogin hook', () => {
|
|||||||
|
|
||||||
it('beforeDeleteFile should call with fileObject', async () => {
|
it('beforeDeleteFile should call with fileObject', async () => {
|
||||||
await reconfigureServer({ filesAdapter: mockAdapter });
|
await reconfigureServer({ filesAdapter: mockAdapter });
|
||||||
Parse.Cloud.beforeDeleteFile((req) => {
|
Parse.Cloud.beforeDeleteFile(req => {
|
||||||
expect(req.file).toBeInstanceOf(Parse.File);
|
expect(req.file).toBeInstanceOf(Parse.File);
|
||||||
expect(req.file._name).toEqual('popeye.txt');
|
expect(req.file._name).toEqual('popeye.txt');
|
||||||
expect(req.file._url).toEqual('http://www.somewhere.com/popeye.txt');
|
expect(req.file._url).toEqual('http://www.somewhere.com/popeye.txt');
|
||||||
@@ -2818,7 +2849,7 @@ describe('beforeLogin hook', () => {
|
|||||||
await file.destroy({ useMasterKey: true });
|
await file.destroy({ useMasterKey: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('beforeDeleteFile should throw error', async (done) => {
|
it('beforeDeleteFile should throw error', async done => {
|
||||||
await reconfigureServer({ filesAdapter: mockAdapter });
|
await reconfigureServer({ filesAdapter: mockAdapter });
|
||||||
Parse.Cloud.beforeDeleteFile(() => {
|
Parse.Cloud.beforeDeleteFile(() => {
|
||||||
throw new Error('some error message');
|
throw new Error('some error message');
|
||||||
@@ -2830,16 +2861,16 @@ describe('beforeLogin hook', () => {
|
|||||||
expect(error.message).toBe('some error message');
|
expect(error.message).toBe('some error message');
|
||||||
done();
|
done();
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
it('afterDeleteFile should call with fileObject', async (done) => {
|
it('afterDeleteFile should call with fileObject', async done => {
|
||||||
await reconfigureServer({ filesAdapter: mockAdapter });
|
await reconfigureServer({ filesAdapter: mockAdapter });
|
||||||
Parse.Cloud.beforeDeleteFile((req) => {
|
Parse.Cloud.beforeDeleteFile(req => {
|
||||||
expect(req.file).toBeInstanceOf(Parse.File);
|
expect(req.file).toBeInstanceOf(Parse.File);
|
||||||
expect(req.file._name).toEqual('popeye.txt');
|
expect(req.file._name).toEqual('popeye.txt');
|
||||||
expect(req.file._url).toEqual('http://www.somewhere.com/popeye.txt');
|
expect(req.file._url).toEqual('http://www.somewhere.com/popeye.txt');
|
||||||
});
|
});
|
||||||
Parse.Cloud.afterDeleteFile((req) => {
|
Parse.Cloud.afterDeleteFile(req => {
|
||||||
expect(req.file).toBeInstanceOf(Parse.File);
|
expect(req.file).toBeInstanceOf(Parse.File);
|
||||||
expect(req.file._name).toEqual('popeye.txt');
|
expect(req.file._name).toEqual('popeye.txt');
|
||||||
expect(req.file._url).toEqual('http://www.somewhere.com/popeye.txt');
|
expect(req.file._url).toEqual('http://www.somewhere.com/popeye.txt');
|
||||||
@@ -2913,10 +2944,10 @@ describe('afterLogin hook', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should have access to context when saving a new object', async () => {
|
it('should have access to context when saving a new object', async () => {
|
||||||
Parse.Cloud.beforeSave('TestObject', (req) => {
|
Parse.Cloud.beforeSave('TestObject', req => {
|
||||||
expect(req.context.a).toEqual('a');
|
expect(req.context.a).toEqual('a');
|
||||||
});
|
});
|
||||||
Parse.Cloud.afterSave('TestObject', (req) => {
|
Parse.Cloud.afterSave('TestObject', req => {
|
||||||
expect(req.context.a).toEqual('a');
|
expect(req.context.a).toEqual('a');
|
||||||
});
|
});
|
||||||
const obj = new TestObject();
|
const obj = new TestObject();
|
||||||
@@ -2926,20 +2957,20 @@ describe('afterLogin hook', () => {
|
|||||||
it('should have access to context when saving an existing object', async () => {
|
it('should have access to context when saving an existing object', async () => {
|
||||||
const obj = new TestObject();
|
const obj = new TestObject();
|
||||||
await obj.save(null);
|
await obj.save(null);
|
||||||
Parse.Cloud.beforeSave('TestObject', (req) => {
|
Parse.Cloud.beforeSave('TestObject', req => {
|
||||||
expect(req.context.a).toEqual('a');
|
expect(req.context.a).toEqual('a');
|
||||||
});
|
});
|
||||||
Parse.Cloud.afterSave('TestObject', (req) => {
|
Parse.Cloud.afterSave('TestObject', req => {
|
||||||
expect(req.context.a).toEqual('a');
|
expect(req.context.a).toEqual('a');
|
||||||
});
|
});
|
||||||
await obj.save(null, { context: { a: 'a' } });
|
await obj.save(null, { context: { a: 'a' } });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should have access to context when saving a new object in a trigger', async () => {
|
it('should have access to context when saving a new object in a trigger', async () => {
|
||||||
Parse.Cloud.beforeSave('TestObject', (req) => {
|
Parse.Cloud.beforeSave('TestObject', req => {
|
||||||
expect(req.context.a).toEqual('a');
|
expect(req.context.a).toEqual('a');
|
||||||
});
|
});
|
||||||
Parse.Cloud.afterSave('TestObject', (req) => {
|
Parse.Cloud.afterSave('TestObject', req => {
|
||||||
expect(req.context.a).toEqual('a');
|
expect(req.context.a).toEqual('a');
|
||||||
});
|
});
|
||||||
Parse.Cloud.afterSave('TriggerObject', async () => {
|
Parse.Cloud.afterSave('TriggerObject', async () => {
|
||||||
@@ -2951,29 +2982,29 @@ describe('afterLogin hook', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should have access to context when cascade-saving objects', async () => {
|
it('should have access to context when cascade-saving objects', async () => {
|
||||||
Parse.Cloud.beforeSave('TestObject', (req) => {
|
Parse.Cloud.beforeSave('TestObject', req => {
|
||||||
expect(req.context.a).toEqual('a');
|
expect(req.context.a).toEqual('a');
|
||||||
});
|
});
|
||||||
Parse.Cloud.afterSave('TestObject', (req) => {
|
Parse.Cloud.afterSave('TestObject', req => {
|
||||||
expect(req.context.a).toEqual('a');
|
expect(req.context.a).toEqual('a');
|
||||||
});
|
});
|
||||||
Parse.Cloud.beforeSave('TestObject2', (req) => {
|
Parse.Cloud.beforeSave('TestObject2', req => {
|
||||||
expect(req.context.a).toEqual('a');
|
expect(req.context.a).toEqual('a');
|
||||||
});
|
});
|
||||||
Parse.Cloud.afterSave('TestObject2', (req) => {
|
Parse.Cloud.afterSave('TestObject2', req => {
|
||||||
expect(req.context.a).toEqual('a');
|
expect(req.context.a).toEqual('a');
|
||||||
});
|
});
|
||||||
const obj = new Parse.Object("TestObject");
|
const obj = new Parse.Object('TestObject');
|
||||||
const obj2 = new Parse.Object("TestObject2");
|
const obj2 = new Parse.Object('TestObject2');
|
||||||
obj.set("obj2", obj2);
|
obj.set('obj2', obj2);
|
||||||
await obj.save(null, { context: { a: 'a' } });
|
await obj.save(null, { context: { a: 'a' } });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should have access to context as saveAll argument', async () => {
|
it('should have access to context as saveAll argument', async () => {
|
||||||
Parse.Cloud.beforeSave('TestObject', (req) => {
|
Parse.Cloud.beforeSave('TestObject', req => {
|
||||||
expect(req.context.a).toEqual('a');
|
expect(req.context.a).toEqual('a');
|
||||||
});
|
});
|
||||||
Parse.Cloud.afterSave('TestObject', (req) => {
|
Parse.Cloud.afterSave('TestObject', req => {
|
||||||
expect(req.context.a).toEqual('a');
|
expect(req.context.a).toEqual('a');
|
||||||
});
|
});
|
||||||
const obj1 = new TestObject();
|
const obj1 = new TestObject();
|
||||||
@@ -2982,10 +3013,10 @@ describe('afterLogin hook', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should have access to context as destroyAll argument', async () => {
|
it('should have access to context as destroyAll argument', async () => {
|
||||||
Parse.Cloud.beforeDelete('TestObject', (req) => {
|
Parse.Cloud.beforeDelete('TestObject', req => {
|
||||||
expect(req.context.a).toEqual('a');
|
expect(req.context.a).toEqual('a');
|
||||||
});
|
});
|
||||||
Parse.Cloud.afterDelete('TestObject', (req) => {
|
Parse.Cloud.afterDelete('TestObject', req => {
|
||||||
expect(req.context.a).toEqual('a');
|
expect(req.context.a).toEqual('a');
|
||||||
});
|
});
|
||||||
const obj1 = new TestObject();
|
const obj1 = new TestObject();
|
||||||
@@ -2995,10 +3026,10 @@ describe('afterLogin hook', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should have access to context as destroy a object', async () => {
|
it('should have access to context as destroy a object', async () => {
|
||||||
Parse.Cloud.beforeDelete('TestObject', (req) => {
|
Parse.Cloud.beforeDelete('TestObject', req => {
|
||||||
expect(req.context.a).toEqual('a');
|
expect(req.context.a).toEqual('a');
|
||||||
});
|
});
|
||||||
Parse.Cloud.afterDelete('TestObject', (req) => {
|
Parse.Cloud.afterDelete('TestObject', req => {
|
||||||
expect(req.context.a).toEqual('a');
|
expect(req.context.a).toEqual('a');
|
||||||
});
|
});
|
||||||
const obj = new TestObject();
|
const obj = new TestObject();
|
||||||
@@ -3007,7 +3038,7 @@ describe('afterLogin hook', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should have access to context in beforeFind hook', async () => {
|
it('should have access to context in beforeFind hook', async () => {
|
||||||
Parse.Cloud.beforeFind('TestObject', (req) => {
|
Parse.Cloud.beforeFind('TestObject', req => {
|
||||||
expect(req.context.a).toEqual('a');
|
expect(req.context.a).toEqual('a');
|
||||||
});
|
});
|
||||||
const query = new Parse.Query('TestObject');
|
const query = new Parse.Query('TestObject');
|
||||||
@@ -3015,7 +3046,7 @@ describe('afterLogin hook', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should have access to context when cloud function is called.', async () => {
|
it('should have access to context when cloud function is called.', async () => {
|
||||||
Parse.Cloud.define('contextTest', async (req) => {
|
Parse.Cloud.define('contextTest', async req => {
|
||||||
expect(req.context.a).toEqual('a');
|
expect(req.context.a).toEqual('a');
|
||||||
return {};
|
return {};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -25,7 +25,8 @@ describe_only_db('mongo')('Idempotency', () => {
|
|||||||
config,
|
config,
|
||||||
auth.master(config),
|
auth.master(config),
|
||||||
'_Idempotency',
|
'_Idempotency',
|
||||||
res.results[0].objectId);
|
res.results[0].objectId
|
||||||
|
);
|
||||||
}
|
}
|
||||||
async function setup(options) {
|
async function setup(options) {
|
||||||
await reconfigureServer({
|
await reconfigureServer({
|
||||||
@@ -37,14 +38,16 @@ describe_only_db('mongo')('Idempotency', () => {
|
|||||||
}
|
}
|
||||||
// Setups
|
// Setups
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
if (SIMULATE_TTL) { jasmine.DEFAULT_TIMEOUT_INTERVAL = 200000; }
|
if (SIMULATE_TTL) {
|
||||||
|
jasmine.DEFAULT_TIMEOUT_INTERVAL = 200000;
|
||||||
|
}
|
||||||
await setup({
|
await setup({
|
||||||
paths: [
|
paths: [
|
||||||
"functions/.*",
|
'functions/.*',
|
||||||
"jobs/.*",
|
'jobs/.*',
|
||||||
"classes/.*",
|
'classes/.*',
|
||||||
"users",
|
'users',
|
||||||
"installations"
|
'installations',
|
||||||
],
|
],
|
||||||
ttl: 30,
|
ttl: 30,
|
||||||
});
|
});
|
||||||
@@ -61,14 +64,14 @@ describe_only_db('mongo')('Idempotency', () => {
|
|||||||
headers: {
|
headers: {
|
||||||
'X-Parse-Application-Id': Parse.applicationId,
|
'X-Parse-Application-Id': Parse.applicationId,
|
||||||
'X-Parse-Master-Key': Parse.masterKey,
|
'X-Parse-Master-Key': Parse.masterKey,
|
||||||
'X-Parse-Request-Id': 'abc-123'
|
'X-Parse-Request-Id': 'abc-123',
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
expect(Config.get(Parse.applicationId).idempotencyOptions.ttl).toBe(30);
|
expect(Config.get(Parse.applicationId).idempotencyOptions.ttl).toBe(30);
|
||||||
await request(params);
|
await request(params);
|
||||||
await request(params).then(fail, e => {
|
await request(params).then(fail, e => {
|
||||||
expect(e.status).toEqual(400);
|
expect(e.status).toEqual(400);
|
||||||
expect(e.data.error).toEqual("Duplicate request");
|
expect(e.data.error).toEqual('Duplicate request');
|
||||||
});
|
});
|
||||||
expect(counter).toBe(1);
|
expect(counter).toBe(1);
|
||||||
});
|
});
|
||||||
@@ -84,8 +87,8 @@ describe_only_db('mongo')('Idempotency', () => {
|
|||||||
headers: {
|
headers: {
|
||||||
'X-Parse-Application-Id': Parse.applicationId,
|
'X-Parse-Application-Id': Parse.applicationId,
|
||||||
'X-Parse-Master-Key': Parse.masterKey,
|
'X-Parse-Master-Key': Parse.masterKey,
|
||||||
'X-Parse-Request-Id': 'abc-123'
|
'X-Parse-Request-Id': 'abc-123',
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
await expectAsync(request(params)).toBeResolved();
|
await expectAsync(request(params)).toBeResolved();
|
||||||
if (SIMULATE_TTL) {
|
if (SIMULATE_TTL) {
|
||||||
@@ -108,13 +111,13 @@ describe_only_db('mongo')('Idempotency', () => {
|
|||||||
headers: {
|
headers: {
|
||||||
'X-Parse-Application-Id': Parse.applicationId,
|
'X-Parse-Application-Id': Parse.applicationId,
|
||||||
'X-Parse-Master-Key': Parse.masterKey,
|
'X-Parse-Master-Key': Parse.masterKey,
|
||||||
'X-Parse-Request-Id': 'abc-123'
|
'X-Parse-Request-Id': 'abc-123',
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
await expectAsync(request(params)).toBeResolved();
|
await expectAsync(request(params)).toBeResolved();
|
||||||
await request(params).then(fail, e => {
|
await request(params).then(fail, e => {
|
||||||
expect(e.status).toEqual(400);
|
expect(e.status).toEqual(400);
|
||||||
expect(e.data.error).toEqual("Duplicate request");
|
expect(e.data.error).toEqual('Duplicate request');
|
||||||
});
|
});
|
||||||
expect(counter).toBe(1);
|
expect(counter).toBe(1);
|
||||||
});
|
});
|
||||||
@@ -130,13 +133,13 @@ describe_only_db('mongo')('Idempotency', () => {
|
|||||||
headers: {
|
headers: {
|
||||||
'X-Parse-Application-Id': Parse.applicationId,
|
'X-Parse-Application-Id': Parse.applicationId,
|
||||||
'X-Parse-Master-Key': Parse.masterKey,
|
'X-Parse-Master-Key': Parse.masterKey,
|
||||||
'X-Parse-Request-Id': 'abc-123'
|
'X-Parse-Request-Id': 'abc-123',
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
await expectAsync(request(params)).toBeResolved();
|
await expectAsync(request(params)).toBeResolved();
|
||||||
await request(params).then(fail, e => {
|
await request(params).then(fail, e => {
|
||||||
expect(e.status).toEqual(400);
|
expect(e.status).toEqual(400);
|
||||||
expect(e.data.error).toEqual("Duplicate request");
|
expect(e.data.error).toEqual('Duplicate request');
|
||||||
});
|
});
|
||||||
expect(counter).toBe(1);
|
expect(counter).toBe(1);
|
||||||
});
|
});
|
||||||
@@ -150,19 +153,19 @@ describe_only_db('mongo')('Idempotency', () => {
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: 'http://localhost:8378/1/users',
|
url: 'http://localhost:8378/1/users',
|
||||||
body: {
|
body: {
|
||||||
username: "user",
|
username: 'user',
|
||||||
password: "pass"
|
password: 'pass',
|
||||||
},
|
},
|
||||||
headers: {
|
headers: {
|
||||||
'X-Parse-Application-Id': Parse.applicationId,
|
'X-Parse-Application-Id': Parse.applicationId,
|
||||||
'X-Parse-Master-Key': Parse.masterKey,
|
'X-Parse-Master-Key': Parse.masterKey,
|
||||||
'X-Parse-Request-Id': 'abc-123'
|
'X-Parse-Request-Id': 'abc-123',
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
await expectAsync(request(params)).toBeResolved();
|
await expectAsync(request(params)).toBeResolved();
|
||||||
await request(params).then(fail, e => {
|
await request(params).then(fail, e => {
|
||||||
expect(e.status).toEqual(400);
|
expect(e.status).toEqual(400);
|
||||||
expect(e.data.error).toEqual("Duplicate request");
|
expect(e.data.error).toEqual('Duplicate request');
|
||||||
});
|
});
|
||||||
expect(counter).toBe(1);
|
expect(counter).toBe(1);
|
||||||
});
|
});
|
||||||
@@ -176,19 +179,19 @@ describe_only_db('mongo')('Idempotency', () => {
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: 'http://localhost:8378/1/installations',
|
url: 'http://localhost:8378/1/installations',
|
||||||
body: {
|
body: {
|
||||||
installationId: "1",
|
installationId: '1',
|
||||||
deviceType: "ios"
|
deviceType: 'ios',
|
||||||
},
|
},
|
||||||
headers: {
|
headers: {
|
||||||
'X-Parse-Application-Id': Parse.applicationId,
|
'X-Parse-Application-Id': Parse.applicationId,
|
||||||
'X-Parse-Master-Key': Parse.masterKey,
|
'X-Parse-Master-Key': Parse.masterKey,
|
||||||
'X-Parse-Request-Id': 'abc-123'
|
'X-Parse-Request-Id': 'abc-123',
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
await expectAsync(request(params)).toBeResolved();
|
await expectAsync(request(params)).toBeResolved();
|
||||||
await request(params).then(fail, e => {
|
await request(params).then(fail, e => {
|
||||||
expect(e.status).toEqual(400);
|
expect(e.status).toEqual(400);
|
||||||
expect(e.data.error).toEqual("Duplicate request");
|
expect(e.data.error).toEqual('Duplicate request');
|
||||||
});
|
});
|
||||||
expect(counter).toBe(1);
|
expect(counter).toBe(1);
|
||||||
});
|
});
|
||||||
@@ -205,8 +208,8 @@ describe_only_db('mongo')('Idempotency', () => {
|
|||||||
headers: {
|
headers: {
|
||||||
'X-Parse-Application-Id': Parse.applicationId,
|
'X-Parse-Application-Id': Parse.applicationId,
|
||||||
'X-Parse-Master-Key': Parse.masterKey,
|
'X-Parse-Master-Key': Parse.masterKey,
|
||||||
'X-Parse-Request-Id': uuid.v4()
|
'X-Parse-Request-Id': uuid.v4(),
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
return request(params);
|
return request(params);
|
||||||
});
|
});
|
||||||
@@ -215,7 +218,9 @@ describe_only_db('mongo')('Idempotency', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should re-throw any other error unchanged when writing request entry fails for any other reason', async () => {
|
it('should re-throw any other error unchanged when writing request entry fails for any other reason', async () => {
|
||||||
spyOn(rest, 'create').and.rejectWith(new Parse.Error(0, "some other error"));
|
spyOn(rest, 'create').and.rejectWith(
|
||||||
|
new Parse.Error(0, 'some other error')
|
||||||
|
);
|
||||||
Parse.Cloud.define('myFunction', () => {});
|
Parse.Cloud.define('myFunction', () => {});
|
||||||
const params = {
|
const params = {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -223,19 +228,23 @@ describe_only_db('mongo')('Idempotency', () => {
|
|||||||
headers: {
|
headers: {
|
||||||
'X-Parse-Application-Id': Parse.applicationId,
|
'X-Parse-Application-Id': Parse.applicationId,
|
||||||
'X-Parse-Master-Key': Parse.masterKey,
|
'X-Parse-Master-Key': Parse.masterKey,
|
||||||
'X-Parse-Request-Id': 'abc-123'
|
'X-Parse-Request-Id': 'abc-123',
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
await request(params).then(fail, e => {
|
await request(params).then(fail, e => {
|
||||||
expect(e.status).toEqual(400);
|
expect(e.status).toEqual(400);
|
||||||
expect(e.data.error).toEqual("some other error");
|
expect(e.data.error).toEqual('some other error');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should use default configuration when none is set', async () => {
|
it('should use default configuration when none is set', async () => {
|
||||||
await setup({});
|
await setup({});
|
||||||
expect(Config.get(Parse.applicationId).idempotencyOptions.ttl).toBe(Definitions.IdempotencyOptions.ttl.default);
|
expect(Config.get(Parse.applicationId).idempotencyOptions.ttl).toBe(
|
||||||
expect(Config.get(Parse.applicationId).idempotencyOptions.paths).toBe(Definitions.IdempotencyOptions.paths.default);
|
Definitions.IdempotencyOptions.ttl.default
|
||||||
|
);
|
||||||
|
expect(Config.get(Parse.applicationId).idempotencyOptions.paths).toBe(
|
||||||
|
Definitions.IdempotencyOptions.paths.default
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw on invalid configuration', async () => {
|
it('should throw on invalid configuration', async () => {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ describe('middlewares', () => {
|
|||||||
_ApplicationId: 'FakeAppId',
|
_ApplicationId: 'FakeAppId',
|
||||||
},
|
},
|
||||||
headers: {},
|
headers: {},
|
||||||
get: (key) => {
|
get: key => {
|
||||||
return fakeReq.headers[key.toLowerCase()];
|
return fakeReq.headers[key.toLowerCase()];
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -24,7 +24,7 @@ describe('middlewares', () => {
|
|||||||
AppCache.del(fakeReq.body._ApplicationId);
|
AppCache.del(fakeReq.body._ApplicationId);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should use _ContentType if provided', (done) => {
|
it('should use _ContentType if provided', done => {
|
||||||
expect(fakeReq.headers['content-type']).toEqual(undefined);
|
expect(fakeReq.headers['content-type']).toEqual(undefined);
|
||||||
const contentType = 'image/jpeg';
|
const contentType = 'image/jpeg';
|
||||||
fakeReq.body._ContentType = contentType;
|
fakeReq.body._ContentType = contentType;
|
||||||
@@ -64,7 +64,7 @@ describe('middlewares', () => {
|
|||||||
expect(fakeRes.status).toHaveBeenCalledWith(403);
|
expect(fakeRes.status).toHaveBeenCalledWith(403);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should succeed when any one of the configured keys supplied', (done) => {
|
it('should succeed when any one of the configured keys supplied', done => {
|
||||||
AppCache.put(fakeReq.body._ApplicationId, {
|
AppCache.put(fakeReq.body._ApplicationId, {
|
||||||
clientKey: 'clientKey',
|
clientKey: 'clientKey',
|
||||||
masterKey: 'masterKey',
|
masterKey: 'masterKey',
|
||||||
@@ -77,7 +77,7 @@ describe('middlewares', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should succeed when client key supplied but empty', (done) => {
|
it('should succeed when client key supplied but empty', done => {
|
||||||
AppCache.put(fakeReq.body._ApplicationId, {
|
AppCache.put(fakeReq.body._ApplicationId, {
|
||||||
clientKey: '',
|
clientKey: '',
|
||||||
masterKey: 'masterKey',
|
masterKey: 'masterKey',
|
||||||
@@ -90,7 +90,7 @@ describe('middlewares', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should succeed when no keys are configured and none supplied', (done) => {
|
it('should succeed when no keys are configured and none supplied', done => {
|
||||||
AppCache.put(fakeReq.body._ApplicationId, {
|
AppCache.put(fakeReq.body._ApplicationId, {
|
||||||
masterKey: 'masterKey',
|
masterKey: 'masterKey',
|
||||||
});
|
});
|
||||||
@@ -110,22 +110,22 @@ describe('middlewares', () => {
|
|||||||
|
|
||||||
const BodyKeys = Object.keys(BodyParams);
|
const BodyKeys = Object.keys(BodyParams);
|
||||||
|
|
||||||
BodyKeys.forEach((infoKey) => {
|
BodyKeys.forEach(infoKey => {
|
||||||
const bodyKey = BodyParams[infoKey];
|
const bodyKey = BodyParams[infoKey];
|
||||||
const keyValue = 'Fake' + bodyKey;
|
const keyValue = 'Fake' + bodyKey;
|
||||||
// javascriptKey is the only one that gets defaulted,
|
// javascriptKey is the only one that gets defaulted,
|
||||||
const otherKeys = BodyKeys.filter(
|
const otherKeys = BodyKeys.filter(
|
||||||
(otherKey) => otherKey !== infoKey && otherKey !== 'javascriptKey'
|
otherKey => otherKey !== infoKey && otherKey !== 'javascriptKey'
|
||||||
);
|
);
|
||||||
|
|
||||||
it(`it should pull ${bodyKey} into req.info`, (done) => {
|
it(`it should pull ${bodyKey} into req.info`, done => {
|
||||||
fakeReq.body[bodyKey] = keyValue;
|
fakeReq.body[bodyKey] = keyValue;
|
||||||
|
|
||||||
middlewares.handleParseHeaders(fakeReq, fakeRes, () => {
|
middlewares.handleParseHeaders(fakeReq, fakeRes, () => {
|
||||||
expect(fakeReq.body[bodyKey]).toEqual(undefined);
|
expect(fakeReq.body[bodyKey]).toEqual(undefined);
|
||||||
expect(fakeReq.info[infoKey]).toEqual(keyValue);
|
expect(fakeReq.info[infoKey]).toEqual(keyValue);
|
||||||
|
|
||||||
otherKeys.forEach((otherKey) => {
|
otherKeys.forEach(otherKey => {
|
||||||
expect(fakeReq.info[otherKey]).toEqual(undefined);
|
expect(fakeReq.info[otherKey]).toEqual(undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -145,7 +145,7 @@ describe('middlewares', () => {
|
|||||||
expect(fakeRes.status).toHaveBeenCalledWith(403);
|
expect(fakeRes.status).toHaveBeenCalledWith(403);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should succeed if the ip does belong to masterKeyIps list', (done) => {
|
it('should succeed if the ip does belong to masterKeyIps list', done => {
|
||||||
AppCache.put(fakeReq.body._ApplicationId, {
|
AppCache.put(fakeReq.body._ApplicationId, {
|
||||||
masterKey: 'masterKey',
|
masterKey: 'masterKey',
|
||||||
masterKeyIps: ['ip1', 'ip2'],
|
masterKeyIps: ['ip1', 'ip2'],
|
||||||
@@ -169,7 +169,7 @@ describe('middlewares', () => {
|
|||||||
expect(fakeRes.status).toHaveBeenCalledWith(403);
|
expect(fakeRes.status).toHaveBeenCalledWith(403);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should succeed if the connection.remoteAddress does belong to masterKeyIps list', (done) => {
|
it('should succeed if the connection.remoteAddress does belong to masterKeyIps list', done => {
|
||||||
AppCache.put(fakeReq.body._ApplicationId, {
|
AppCache.put(fakeReq.body._ApplicationId, {
|
||||||
masterKey: 'masterKey',
|
masterKey: 'masterKey',
|
||||||
masterKeyIps: ['ip1', 'ip2'],
|
masterKeyIps: ['ip1', 'ip2'],
|
||||||
@@ -193,7 +193,7 @@ describe('middlewares', () => {
|
|||||||
expect(fakeRes.status).toHaveBeenCalledWith(403);
|
expect(fakeRes.status).toHaveBeenCalledWith(403);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should succeed if the socket.remoteAddress does belong to masterKeyIps list', (done) => {
|
it('should succeed if the socket.remoteAddress does belong to masterKeyIps list', done => {
|
||||||
AppCache.put(fakeReq.body._ApplicationId, {
|
AppCache.put(fakeReq.body._ApplicationId, {
|
||||||
masterKey: 'masterKey',
|
masterKey: 'masterKey',
|
||||||
masterKeyIps: ['ip1', 'ip2'],
|
masterKeyIps: ['ip1', 'ip2'],
|
||||||
@@ -217,7 +217,7 @@ describe('middlewares', () => {
|
|||||||
expect(fakeRes.status).toHaveBeenCalledWith(403);
|
expect(fakeRes.status).toHaveBeenCalledWith(403);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should succeed if the connection.socket.remoteAddress does belong to masterKeyIps list', (done) => {
|
it('should succeed if the connection.socket.remoteAddress does belong to masterKeyIps list', done => {
|
||||||
AppCache.put(fakeReq.body._ApplicationId, {
|
AppCache.put(fakeReq.body._ApplicationId, {
|
||||||
masterKey: 'masterKey',
|
masterKey: 'masterKey',
|
||||||
masterKeyIps: ['ip1', 'ip2'],
|
masterKeyIps: ['ip1', 'ip2'],
|
||||||
@@ -230,7 +230,7 @@ describe('middlewares', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should allow any ip to use masterKey if masterKeyIps is empty', (done) => {
|
it('should allow any ip to use masterKey if masterKeyIps is empty', done => {
|
||||||
AppCache.put(fakeReq.body._ApplicationId, {
|
AppCache.put(fakeReq.body._ApplicationId, {
|
||||||
masterKey: 'masterKey',
|
masterKey: 'masterKey',
|
||||||
masterKeyIps: [],
|
masterKeyIps: [],
|
||||||
@@ -243,7 +243,7 @@ describe('middlewares', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should succeed if xff header does belong to masterKeyIps', (done) => {
|
it('should succeed if xff header does belong to masterKeyIps', done => {
|
||||||
AppCache.put(fakeReq.body._ApplicationId, {
|
AppCache.put(fakeReq.body._ApplicationId, {
|
||||||
masterKey: 'masterKey',
|
masterKey: 'masterKey',
|
||||||
masterKeyIps: ['ip1'],
|
masterKeyIps: ['ip1'],
|
||||||
@@ -256,7 +256,7 @@ describe('middlewares', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should succeed if xff header with one ip does belong to masterKeyIps', (done) => {
|
it('should succeed if xff header with one ip does belong to masterKeyIps', done => {
|
||||||
AppCache.put(fakeReq.body._ApplicationId, {
|
AppCache.put(fakeReq.body._ApplicationId, {
|
||||||
masterKey: 'masterKey',
|
masterKey: 'masterKey',
|
||||||
masterKeyIps: ['ip1'],
|
masterKeyIps: ['ip1'],
|
||||||
@@ -393,7 +393,7 @@ describe('middlewares', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should use user provided on field userFromJWT', (done) => {
|
it('should use user provided on field userFromJWT', done => {
|
||||||
AppCache.put(fakeReq.body._ApplicationId, {
|
AppCache.put(fakeReq.body._ApplicationId, {
|
||||||
masterKey: 'masterKey',
|
masterKey: 'masterKey',
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -354,14 +354,12 @@ describe_only_db('mongo')('MongoStorageAdapter', () => {
|
|||||||
it('should delete field without index', async () => {
|
it('should delete field without index', async () => {
|
||||||
const database = Config.get(Parse.applicationId).database;
|
const database = Config.get(Parse.applicationId).database;
|
||||||
const obj = new Parse.Object('MyObject');
|
const obj = new Parse.Object('MyObject');
|
||||||
obj.set("test", 1);
|
obj.set('test', 1);
|
||||||
await obj.save();
|
await obj.save();
|
||||||
const schemaBeforeDeletion = await new Parse.Schema('MyObject').get();
|
const schemaBeforeDeletion = await new Parse.Schema('MyObject').get();
|
||||||
await database.adapter.deleteFields(
|
await database.adapter.deleteFields('MyObject', schemaBeforeDeletion, [
|
||||||
"MyObject",
|
'test',
|
||||||
schemaBeforeDeletion,
|
]);
|
||||||
["test"]
|
|
||||||
);
|
|
||||||
const schemaAfterDeletion = await new Parse.Schema('MyObject').get();
|
const schemaAfterDeletion = await new Parse.Schema('MyObject').get();
|
||||||
expect(schemaBeforeDeletion.fields.test).toBeDefined();
|
expect(schemaBeforeDeletion.fields.test).toBeDefined();
|
||||||
expect(schemaAfterDeletion.fields.test).toBeUndefined();
|
expect(schemaAfterDeletion.fields.test).toBeUndefined();
|
||||||
@@ -370,19 +368,15 @@ describe_only_db('mongo')('MongoStorageAdapter', () => {
|
|||||||
it('should delete field with index', async () => {
|
it('should delete field with index', async () => {
|
||||||
const database = Config.get(Parse.applicationId).database;
|
const database = Config.get(Parse.applicationId).database;
|
||||||
const obj = new Parse.Object('MyObject');
|
const obj = new Parse.Object('MyObject');
|
||||||
obj.set("test", 1);
|
obj.set('test', 1);
|
||||||
await obj.save();
|
await obj.save();
|
||||||
const schemaBeforeDeletion = await new Parse.Schema('MyObject').get();
|
const schemaBeforeDeletion = await new Parse.Schema('MyObject').get();
|
||||||
await database.adapter.ensureIndex(
|
await database.adapter.ensureIndex('MyObject', schemaBeforeDeletion, [
|
||||||
'MyObject',
|
'test',
|
||||||
schemaBeforeDeletion,
|
]);
|
||||||
['test']
|
await database.adapter.deleteFields('MyObject', schemaBeforeDeletion, [
|
||||||
);
|
'test',
|
||||||
await database.adapter.deleteFields(
|
]);
|
||||||
"MyObject",
|
|
||||||
schemaBeforeDeletion,
|
|
||||||
["test"]
|
|
||||||
);
|
|
||||||
const schemaAfterDeletion = await new Parse.Schema('MyObject').get();
|
const schemaAfterDeletion = await new Parse.Schema('MyObject').get();
|
||||||
expect(schemaBeforeDeletion.fields.test).toBeDefined();
|
expect(schemaBeforeDeletion.fields.test).toBeDefined();
|
||||||
expect(schemaAfterDeletion.fields.test).toBeUndefined();
|
expect(schemaAfterDeletion.fields.test).toBeUndefined();
|
||||||
|
|||||||
@@ -1440,13 +1440,25 @@ describe('Parse.Query Aggregate testing', () => {
|
|||||||
['location'],
|
['location'],
|
||||||
undefined,
|
undefined,
|
||||||
false,
|
false,
|
||||||
{ indexType: '2dsphere' },
|
{ indexType: '2dsphere' }
|
||||||
);
|
);
|
||||||
// Create objects
|
// Create objects
|
||||||
const GeoObject = Parse.Object.extend('GeoObject');
|
const GeoObject = Parse.Object.extend('GeoObject');
|
||||||
const obj1 = new GeoObject({ value: 1, location: new Parse.GeoPoint(1, 1), date: new Date(1) });
|
const obj1 = new GeoObject({
|
||||||
const obj2 = new GeoObject({ value: 2, location: new Parse.GeoPoint(2, 1), date: new Date(2) });
|
value: 1,
|
||||||
const obj3 = new GeoObject({ value: 3, location: new Parse.GeoPoint(3, 1), date: new Date(3) });
|
location: new Parse.GeoPoint(1, 1),
|
||||||
|
date: new Date(1),
|
||||||
|
});
|
||||||
|
const obj2 = new GeoObject({
|
||||||
|
value: 2,
|
||||||
|
location: new Parse.GeoPoint(2, 1),
|
||||||
|
date: new Date(2),
|
||||||
|
});
|
||||||
|
const obj3 = new GeoObject({
|
||||||
|
value: 3,
|
||||||
|
location: new Parse.GeoPoint(3, 1),
|
||||||
|
date: new Date(3),
|
||||||
|
});
|
||||||
await Parse.Object.saveAll([obj1, obj2, obj3]);
|
await Parse.Object.saveAll([obj1, obj2, obj3]);
|
||||||
// Create query
|
// Create query
|
||||||
const pipeline = [
|
const pipeline = [
|
||||||
@@ -1454,18 +1466,18 @@ describe('Parse.Query Aggregate testing', () => {
|
|||||||
geoNear: {
|
geoNear: {
|
||||||
near: {
|
near: {
|
||||||
type: 'Point',
|
type: 'Point',
|
||||||
coordinates: [1, 1]
|
coordinates: [1, 1],
|
||||||
},
|
},
|
||||||
key: 'location',
|
key: 'location',
|
||||||
spherical: true,
|
spherical: true,
|
||||||
distanceField: 'dist',
|
distanceField: 'dist',
|
||||||
query: {
|
query: {
|
||||||
date: {
|
date: {
|
||||||
$gte: new Date(2)
|
$gte: new Date(2),
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
const query = new Parse.Query(GeoObject);
|
const query = new Parse.Query(GeoObject);
|
||||||
const results = await query.aggregate(pipeline);
|
const results = await query.aggregate(pipeline);
|
||||||
@@ -1489,9 +1501,21 @@ describe('Parse.Query Aggregate testing', () => {
|
|||||||
);
|
);
|
||||||
// Create objects
|
// Create objects
|
||||||
const GeoObject = Parse.Object.extend('GeoObject');
|
const GeoObject = Parse.Object.extend('GeoObject');
|
||||||
const obj1 = new GeoObject({ value: 1, location: new Parse.GeoPoint(1, 1), date: new Date(1) });
|
const obj1 = new GeoObject({
|
||||||
const obj2 = new GeoObject({ value: 2, location: new Parse.GeoPoint(2, 1), date: new Date(2) });
|
value: 1,
|
||||||
const obj3 = new GeoObject({ value: 3, location: new Parse.GeoPoint(3, 1), date: new Date(3) });
|
location: new Parse.GeoPoint(1, 1),
|
||||||
|
date: new Date(1),
|
||||||
|
});
|
||||||
|
const obj2 = new GeoObject({
|
||||||
|
value: 2,
|
||||||
|
location: new Parse.GeoPoint(2, 1),
|
||||||
|
date: new Date(2),
|
||||||
|
});
|
||||||
|
const obj3 = new GeoObject({
|
||||||
|
value: 3,
|
||||||
|
location: new Parse.GeoPoint(3, 1),
|
||||||
|
date: new Date(3),
|
||||||
|
});
|
||||||
await Parse.Object.saveAll([obj1, obj2, obj3]);
|
await Parse.Object.saveAll([obj1, obj2, obj3]);
|
||||||
// Create query
|
// Create query
|
||||||
const pipeline = [
|
const pipeline = [
|
||||||
@@ -1499,13 +1523,13 @@ describe('Parse.Query Aggregate testing', () => {
|
|||||||
geoNear: {
|
geoNear: {
|
||||||
near: {
|
near: {
|
||||||
type: 'Point',
|
type: 'Point',
|
||||||
coordinates: [1, 1]
|
coordinates: [1, 1],
|
||||||
},
|
},
|
||||||
key: 'location',
|
key: 'location',
|
||||||
spherical: true,
|
spherical: true,
|
||||||
distanceField: 'dist'
|
distanceField: 'dist',
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
const query = new Parse.Query(GeoObject);
|
const query = new Parse.Query(GeoObject);
|
||||||
const results = await query.aggregate(pipeline);
|
const results = await query.aggregate(pipeline);
|
||||||
@@ -1513,7 +1537,9 @@ describe('Parse.Query Aggregate testing', () => {
|
|||||||
expect(results.length).toEqual(3);
|
expect(results.length).toEqual(3);
|
||||||
});
|
});
|
||||||
|
|
||||||
it_only_db('mongo')('aggregate geoNear with near legacy coordinate pair', async () => {
|
it_only_db('mongo')(
|
||||||
|
'aggregate geoNear with near legacy coordinate pair',
|
||||||
|
async () => {
|
||||||
// Create geo index which is required for `geoNear` query
|
// Create geo index which is required for `geoNear` query
|
||||||
const database = Config.get(Parse.applicationId).database;
|
const database = Config.get(Parse.applicationId).database;
|
||||||
const schema = await new Parse.Schema('GeoObject').save();
|
const schema = await new Parse.Schema('GeoObject').save();
|
||||||
@@ -1527,9 +1553,21 @@ describe('Parse.Query Aggregate testing', () => {
|
|||||||
);
|
);
|
||||||
// Create objects
|
// Create objects
|
||||||
const GeoObject = Parse.Object.extend('GeoObject');
|
const GeoObject = Parse.Object.extend('GeoObject');
|
||||||
const obj1 = new GeoObject({ value: 1, location: new Parse.GeoPoint(1, 1), date: new Date(1) });
|
const obj1 = new GeoObject({
|
||||||
const obj2 = new GeoObject({ value: 2, location: new Parse.GeoPoint(2, 1), date: new Date(2) });
|
value: 1,
|
||||||
const obj3 = new GeoObject({ value: 3, location: new Parse.GeoPoint(3, 1), date: new Date(3) });
|
location: new Parse.GeoPoint(1, 1),
|
||||||
|
date: new Date(1),
|
||||||
|
});
|
||||||
|
const obj2 = new GeoObject({
|
||||||
|
value: 2,
|
||||||
|
location: new Parse.GeoPoint(2, 1),
|
||||||
|
date: new Date(2),
|
||||||
|
});
|
||||||
|
const obj3 = new GeoObject({
|
||||||
|
value: 3,
|
||||||
|
location: new Parse.GeoPoint(3, 1),
|
||||||
|
date: new Date(3),
|
||||||
|
});
|
||||||
await Parse.Object.saveAll([obj1, obj2, obj3]);
|
await Parse.Object.saveAll([obj1, obj2, obj3]);
|
||||||
// Create query
|
// Create query
|
||||||
const pipeline = [
|
const pipeline = [
|
||||||
@@ -1538,13 +1576,14 @@ describe('Parse.Query Aggregate testing', () => {
|
|||||||
near: [1, 1],
|
near: [1, 1],
|
||||||
key: 'location',
|
key: 'location',
|
||||||
spherical: true,
|
spherical: true,
|
||||||
distanceField: 'dist'
|
distanceField: 'dist',
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
const query = new Parse.Query(GeoObject);
|
const query = new Parse.Query(GeoObject);
|
||||||
const results = await query.aggregate(pipeline);
|
const results = await query.aggregate(pipeline);
|
||||||
// Check results
|
// Check results
|
||||||
expect(results.length).toEqual(3);
|
expect(results.length).toEqual(3);
|
||||||
});
|
}
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3362,10 +3362,7 @@ describe('Parse.User testing', () => {
|
|||||||
user
|
user
|
||||||
.signUp()
|
.signUp()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
return Parse.User.current()
|
return Parse.User.current().relation('relation').query().find();
|
||||||
.relation('relation')
|
|
||||||
.query()
|
|
||||||
.find();
|
|
||||||
})
|
})
|
||||||
.then(res => {
|
.then(res => {
|
||||||
expect(res.length).toBe(0);
|
expect(res.length).toBe(0);
|
||||||
@@ -3401,9 +3398,7 @@ describe('Parse.User testing', () => {
|
|||||||
return user.signUp();
|
return user.signUp();
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
return Parse.User.current()
|
return Parse.User.current().set('emailVerified', true).save();
|
||||||
.set('emailVerified', true)
|
|
||||||
.save();
|
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
fail('Should not be able to update emailVerified');
|
fail('Should not be able to update emailVerified');
|
||||||
@@ -3574,9 +3569,7 @@ describe('Parse.User testing', () => {
|
|||||||
return user.signUp();
|
return user.signUp();
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
return Parse.User.current()
|
return Parse.User.current().set('_email_verify_token', 'bad').save();
|
||||||
.set('_email_verify_token', 'bad')
|
|
||||||
.save();
|
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
fail('Should not be able to update email verification token');
|
fail('Should not be able to update email verification token');
|
||||||
|
|||||||
@@ -219,9 +219,9 @@ describe('batch', () => {
|
|||||||
expect(databaseAdapter.createObject.calls.argsFor(0)[3]).toBe(
|
expect(databaseAdapter.createObject.calls.argsFor(0)[3]).toBe(
|
||||||
databaseAdapter.createObject.calls.argsFor(1)[3]
|
databaseAdapter.createObject.calls.argsFor(1)[3]
|
||||||
);
|
);
|
||||||
expect(results.map(result => result.get('key')).sort()).toEqual(
|
expect(
|
||||||
['value1', 'value2']
|
results.map(result => result.get('key')).sort()
|
||||||
);
|
).toEqual(['value1', 'value2']);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -473,7 +473,7 @@ describe('defaultGraphQLTypes', () => {
|
|||||||
|
|
||||||
it('should serialize date', () => {
|
it('should serialize date', () => {
|
||||||
const date = new Date();
|
const date = new Date();
|
||||||
expect(serialize(date)).toBe(date.toUTCString());
|
expect(serialize(date)).toBe(date.toISOString());
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return iso value if object', () => {
|
it('should return iso value if object', () => {
|
||||||
|
|||||||
@@ -230,7 +230,7 @@ afterEach(function(done) {
|
|||||||
'_Session',
|
'_Session',
|
||||||
'_Product',
|
'_Product',
|
||||||
'_Audience',
|
'_Audience',
|
||||||
'_Idempotency'
|
'_Idempotency',
|
||||||
].indexOf(className) >= 0
|
].indexOf(className) >= 0
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
"use strict";
|
'use strict';
|
||||||
|
|
||||||
// Helper functions for accessing the google API.
|
// Helper functions for accessing the google API.
|
||||||
var Parse = require('parse/node').Parse;
|
var Parse = require('parse/node').Parse;
|
||||||
@@ -11,7 +11,6 @@ const HTTPS_TOKEN_ISSUER = 'https://accounts.google.com';
|
|||||||
|
|
||||||
let cache = {};
|
let cache = {};
|
||||||
|
|
||||||
|
|
||||||
// Retrieve Google Signin Keys (with cache control)
|
// Retrieve Google Signin Keys (with cache control)
|
||||||
function getGoogleKeyByKeyId(keyId) {
|
function getGoogleKeyByKeyId(keyId) {
|
||||||
if (cache[keyId] && cache.expiresAt > new Date()) {
|
if (cache[keyId] && cache.expiresAt > new Date()) {
|
||||||
@@ -19,26 +18,38 @@ function getGoogleKeyByKeyId(keyId) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
https.get(`https://www.googleapis.com/oauth2/v3/certs`, res => {
|
https
|
||||||
|
.get(`https://www.googleapis.com/oauth2/v3/certs`, res => {
|
||||||
let data = '';
|
let data = '';
|
||||||
res.on('data', chunk => {
|
res.on('data', chunk => {
|
||||||
data += chunk.toString('utf8');
|
data += chunk.toString('utf8');
|
||||||
});
|
});
|
||||||
res.on('end', () => {
|
res.on('end', () => {
|
||||||
const { keys } = JSON.parse(data);
|
const { keys } = JSON.parse(data);
|
||||||
const pems = keys.reduce((pems, {n: modulus, e: exposant, kid}) => Object.assign(pems, {[kid]: rsaPublicKeyToPEM(modulus, exposant)}), {});
|
const pems = keys.reduce(
|
||||||
|
(pems, { n: modulus, e: exposant, kid }) =>
|
||||||
|
Object.assign(pems, {
|
||||||
|
[kid]: rsaPublicKeyToPEM(modulus, exposant),
|
||||||
|
}),
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
|
||||||
if (res.headers['cache-control']) {
|
if (res.headers['cache-control']) {
|
||||||
var expire = res.headers['cache-control'].match(/max-age=([0-9]+)/);
|
var expire = res.headers['cache-control'].match(/max-age=([0-9]+)/);
|
||||||
|
|
||||||
if (expire) {
|
if (expire) {
|
||||||
cache = Object.assign({}, pems, {expiresAt: new Date((new Date()).getTime() + Number(expire[1]) * 1000)});
|
cache = Object.assign({}, pems, {
|
||||||
|
expiresAt: new Date(
|
||||||
|
new Date().getTime() + Number(expire[1]) * 1000
|
||||||
|
),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve(pems[keyId]);
|
resolve(pems[keyId]);
|
||||||
});
|
});
|
||||||
}).on('error', reject);
|
})
|
||||||
|
.on('error', reject);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,7 +57,10 @@ function getHeaderFromToken(token) {
|
|||||||
const decodedToken = jwt.decode(token, { complete: true });
|
const decodedToken = jwt.decode(token, { complete: true });
|
||||||
|
|
||||||
if (!decodedToken) {
|
if (!decodedToken) {
|
||||||
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, `provided token does not decode as JWT`);
|
throw new Parse.Error(
|
||||||
|
Parse.Error.OBJECT_NOT_FOUND,
|
||||||
|
`provided token does not decode as JWT`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return decodedToken.header;
|
return decodedToken.header;
|
||||||
@@ -54,7 +68,10 @@ function getHeaderFromToken(token) {
|
|||||||
|
|
||||||
async function verifyIdToken({ id_token: token, id }, { clientId }) {
|
async function verifyIdToken({ id_token: token, id }, { clientId }) {
|
||||||
if (!token) {
|
if (!token) {
|
||||||
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, `id token is invalid for this user.`);
|
throw new Parse.Error(
|
||||||
|
Parse.Error.OBJECT_NOT_FOUND,
|
||||||
|
`id token is invalid for this user.`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { kid: keyId, alg: algorithm } = getHeaderFromToken(token);
|
const { kid: keyId, alg: algorithm } = getHeaderFromToken(token);
|
||||||
@@ -62,22 +79,34 @@ async function verifyIdToken({id_token: token, id}, {clientId}) {
|
|||||||
const googleKey = await getGoogleKeyByKeyId(keyId);
|
const googleKey = await getGoogleKeyByKeyId(keyId);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
jwtClaims = jwt.verify(token, googleKey, { algorithms: algorithm, audience: clientId });
|
jwtClaims = jwt.verify(token, googleKey, {
|
||||||
|
algorithms: algorithm,
|
||||||
|
audience: clientId,
|
||||||
|
});
|
||||||
} catch (exception) {
|
} catch (exception) {
|
||||||
const message = exception.message;
|
const message = exception.message;
|
||||||
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, `${message}`);
|
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, `${message}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (jwtClaims.iss !== TOKEN_ISSUER && jwtClaims.iss !== HTTPS_TOKEN_ISSUER) {
|
if (jwtClaims.iss !== TOKEN_ISSUER && jwtClaims.iss !== HTTPS_TOKEN_ISSUER) {
|
||||||
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, `id token not issued by correct provider - expected: ${TOKEN_ISSUER} or ${HTTPS_TOKEN_ISSUER} | from: ${jwtClaims.iss}`);
|
throw new Parse.Error(
|
||||||
|
Parse.Error.OBJECT_NOT_FOUND,
|
||||||
|
`id token not issued by correct provider - expected: ${TOKEN_ISSUER} or ${HTTPS_TOKEN_ISSUER} | from: ${jwtClaims.iss}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (jwtClaims.sub !== id) {
|
if (jwtClaims.sub !== id) {
|
||||||
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, `auth data is invalid for this user.`);
|
throw new Parse.Error(
|
||||||
|
Parse.Error.OBJECT_NOT_FOUND,
|
||||||
|
`auth data is invalid for this user.`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (clientId && jwtClaims.aud !== clientId) {
|
if (clientId && jwtClaims.aud !== clientId) {
|
||||||
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, `id token not authorized for this clientId.`);
|
throw new Parse.Error(
|
||||||
|
Parse.Error.OBJECT_NOT_FOUND,
|
||||||
|
`id token not authorized for this clientId.`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return jwtClaims;
|
return jwtClaims;
|
||||||
@@ -95,10 +124,9 @@ function validateAppId() {
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
validateAppId: validateAppId,
|
validateAppId: validateAppId,
|
||||||
validateAuthData: validateAuthData
|
validateAuthData: validateAuthData,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// Helpers functions to convert the RSA certs to PEM (from jwks-rsa)
|
// Helpers functions to convert the RSA certs to PEM (from jwks-rsa)
|
||||||
function rsaPublicKeyToPEM(modulusB64, exponentB64) {
|
function rsaPublicKeyToPEM(modulusB64, exponentB64) {
|
||||||
const modulus = new Buffer(modulusB64, 'base64');
|
const modulus = new Buffer(modulusB64, 'base64');
|
||||||
@@ -110,13 +138,19 @@ function rsaPublicKeyToPEM(modulusB64, exponentB64) {
|
|||||||
|
|
||||||
const encodedModlen = encodeLengthHex(modlen);
|
const encodedModlen = encodeLengthHex(modlen);
|
||||||
const encodedExplen = encodeLengthHex(explen);
|
const encodedExplen = encodeLengthHex(explen);
|
||||||
const encodedPubkey = '30' +
|
const encodedPubkey =
|
||||||
encodeLengthHex(modlen + explen + encodedModlen.length / 2 + encodedExplen.length / 2 + 2) +
|
'30' +
|
||||||
'02' + encodedModlen + modulusHex +
|
encodeLengthHex(
|
||||||
'02' + encodedExplen + exponentHex;
|
modlen + explen + encodedModlen.length / 2 + encodedExplen.length / 2 + 2
|
||||||
|
) +
|
||||||
|
'02' +
|
||||||
|
encodedModlen +
|
||||||
|
modulusHex +
|
||||||
|
'02' +
|
||||||
|
encodedExplen +
|
||||||
|
exponentHex;
|
||||||
|
|
||||||
const der = new Buffer(encodedPubkey, 'hex')
|
const der = new Buffer(encodedPubkey, 'hex').toString('base64');
|
||||||
.toString('base64');
|
|
||||||
|
|
||||||
let pem = '-----BEGIN RSA PUBLIC KEY-----\n';
|
let pem = '-----BEGIN RSA PUBLIC KEY-----\n';
|
||||||
pem += `${der.match(/.{1,64}/g).join('\n')}`;
|
pem += `${der.match(/.{1,64}/g).join('\n')}`;
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ export class GridFSBucketAdapter extends FilesAdapter {
|
|||||||
this._connectionPromise = MongoClient.connect(
|
this._connectionPromise = MongoClient.connect(
|
||||||
this._databaseURI,
|
this._databaseURI,
|
||||||
this._mongoOptions
|
this._mongoOptions
|
||||||
).then((client) => {
|
).then(client => {
|
||||||
this._client = client;
|
this._client = client;
|
||||||
return client.db(client.s.options.dbName);
|
return client.db(client.s.options.dbName);
|
||||||
});
|
});
|
||||||
@@ -55,7 +55,7 @@ export class GridFSBucketAdapter extends FilesAdapter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_getBucket() {
|
_getBucket() {
|
||||||
return this._connect().then((database) => new GridFSBucket(database));
|
return this._connect().then(database => new GridFSBucket(database));
|
||||||
}
|
}
|
||||||
|
|
||||||
// For a given config object, filename, and data, store a file
|
// For a given config object, filename, and data, store a file
|
||||||
@@ -92,7 +92,7 @@ export class GridFSBucketAdapter extends FilesAdapter {
|
|||||||
throw new Error('FileNotFound');
|
throw new Error('FileNotFound');
|
||||||
}
|
}
|
||||||
return Promise.all(
|
return Promise.all(
|
||||||
documents.map((doc) => {
|
documents.map(doc => {
|
||||||
return bucket.delete(doc._id);
|
return bucket.delete(doc._id);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -104,7 +104,7 @@ export class GridFSBucketAdapter extends FilesAdapter {
|
|||||||
stream.read();
|
stream.read();
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const chunks = [];
|
const chunks = [];
|
||||||
stream.on('data', (data) => {
|
stream.on('data', data => {
|
||||||
chunks.push(data);
|
chunks.push(data);
|
||||||
});
|
});
|
||||||
stream.on('end', () => {
|
stream.on('end', () => {
|
||||||
@@ -127,7 +127,7 @@ export class GridFSBucketAdapter extends FilesAdapter {
|
|||||||
}
|
}
|
||||||
resolve(data);
|
resolve(data);
|
||||||
});
|
});
|
||||||
stream.on('error', (err) => {
|
stream.on('error', err => {
|
||||||
reject(err);
|
reject(err);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -177,7 +177,7 @@ export class GridFSBucketAdapter extends FilesAdapter {
|
|||||||
});
|
});
|
||||||
const stream = bucket.openDownloadStreamByName(filename);
|
const stream = bucket.openDownloadStreamByName(filename);
|
||||||
stream.start(start);
|
stream.start(start);
|
||||||
stream.on('data', (chunk) => {
|
stream.on('data', chunk => {
|
||||||
res.write(chunk);
|
res.write(chunk);
|
||||||
});
|
});
|
||||||
stream.on('error', () => {
|
stream.on('error', () => {
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ const debug = function (...args: any) {
|
|||||||
import { StorageAdapter } from '../StorageAdapter';
|
import { StorageAdapter } from '../StorageAdapter';
|
||||||
import type { SchemaType, QueryType, QueryOptions } from '../StorageAdapter';
|
import type { SchemaType, QueryType, QueryOptions } from '../StorageAdapter';
|
||||||
|
|
||||||
const parseTypeToPostgresType = (type) => {
|
const parseTypeToPostgresType = type => {
|
||||||
switch (type.type) {
|
switch (type.type) {
|
||||||
case 'String':
|
case 'String':
|
||||||
return 'text';
|
return 'text';
|
||||||
@@ -79,7 +79,7 @@ const mongoAggregateToPostgres = {
|
|||||||
$year: 'YEAR',
|
$year: 'YEAR',
|
||||||
};
|
};
|
||||||
|
|
||||||
const toPostgresValue = (value) => {
|
const toPostgresValue = value => {
|
||||||
if (typeof value === 'object') {
|
if (typeof value === 'object') {
|
||||||
if (value.__type === 'Date') {
|
if (value.__type === 'Date') {
|
||||||
return value.iso;
|
return value.iso;
|
||||||
@@ -91,7 +91,7 @@ const toPostgresValue = (value) => {
|
|||||||
return value;
|
return value;
|
||||||
};
|
};
|
||||||
|
|
||||||
const transformValue = (value) => {
|
const transformValue = value => {
|
||||||
if (typeof value === 'object' && value.__type === 'Pointer') {
|
if (typeof value === 'object' && value.__type === 'Pointer') {
|
||||||
return value.objectId;
|
return value.objectId;
|
||||||
}
|
}
|
||||||
@@ -121,7 +121,7 @@ const defaultCLPS = Object.freeze({
|
|||||||
protectedFields: { '*': [] },
|
protectedFields: { '*': [] },
|
||||||
});
|
});
|
||||||
|
|
||||||
const toParseSchema = (schema) => {
|
const toParseSchema = schema => {
|
||||||
if (schema.className === '_User') {
|
if (schema.className === '_User') {
|
||||||
delete schema.fields._hashed_password;
|
delete schema.fields._hashed_password;
|
||||||
}
|
}
|
||||||
@@ -145,7 +145,7 @@ const toParseSchema = (schema) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const toPostgresSchema = (schema) => {
|
const toPostgresSchema = schema => {
|
||||||
if (!schema) {
|
if (!schema) {
|
||||||
return schema;
|
return schema;
|
||||||
}
|
}
|
||||||
@@ -159,8 +159,8 @@ const toPostgresSchema = (schema) => {
|
|||||||
return schema;
|
return schema;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDotFields = (object) => {
|
const handleDotFields = object => {
|
||||||
Object.keys(object).forEach((fieldName) => {
|
Object.keys(object).forEach(fieldName => {
|
||||||
if (fieldName.indexOf('.') > -1) {
|
if (fieldName.indexOf('.') > -1) {
|
||||||
const components = fieldName.split('.');
|
const components = fieldName.split('.');
|
||||||
const first = components.shift();
|
const first = components.shift();
|
||||||
@@ -186,7 +186,7 @@ const handleDotFields = (object) => {
|
|||||||
return object;
|
return object;
|
||||||
};
|
};
|
||||||
|
|
||||||
const transformDotFieldToComponents = (fieldName) => {
|
const transformDotFieldToComponents = fieldName => {
|
||||||
return fieldName.split('.').map((cmpt, index) => {
|
return fieldName.split('.').map((cmpt, index) => {
|
||||||
if (index === 0) {
|
if (index === 0) {
|
||||||
return `"${cmpt}"`;
|
return `"${cmpt}"`;
|
||||||
@@ -195,7 +195,7 @@ const transformDotFieldToComponents = (fieldName) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const transformDotField = (fieldName) => {
|
const transformDotField = fieldName => {
|
||||||
if (fieldName.indexOf('.') === -1) {
|
if (fieldName.indexOf('.') === -1) {
|
||||||
return `"${fieldName}"`;
|
return `"${fieldName}"`;
|
||||||
}
|
}
|
||||||
@@ -205,7 +205,7 @@ const transformDotField = (fieldName) => {
|
|||||||
return name;
|
return name;
|
||||||
};
|
};
|
||||||
|
|
||||||
const transformAggregateField = (fieldName) => {
|
const transformAggregateField = fieldName => {
|
||||||
if (typeof fieldName !== 'string') {
|
if (typeof fieldName !== 'string') {
|
||||||
return fieldName;
|
return fieldName;
|
||||||
}
|
}
|
||||||
@@ -218,7 +218,7 @@ const transformAggregateField = (fieldName) => {
|
|||||||
return fieldName.substr(1);
|
return fieldName.substr(1);
|
||||||
};
|
};
|
||||||
|
|
||||||
const validateKeys = (object) => {
|
const validateKeys = object => {
|
||||||
if (typeof object == 'object') {
|
if (typeof object == 'object') {
|
||||||
for (const key in object) {
|
for (const key in object) {
|
||||||
if (typeof object[key] == 'object') {
|
if (typeof object[key] == 'object') {
|
||||||
@@ -236,10 +236,10 @@ const validateKeys = (object) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Returns the list of join tables on a schema
|
// Returns the list of join tables on a schema
|
||||||
const joinTablesForSchema = (schema) => {
|
const joinTablesForSchema = schema => {
|
||||||
const list = [];
|
const list = [];
|
||||||
if (schema) {
|
if (schema) {
|
||||||
Object.keys(schema.fields).forEach((field) => {
|
Object.keys(schema.fields).forEach(field => {
|
||||||
if (schema.fields[field].type === 'Relation') {
|
if (schema.fields[field].type === 'Relation') {
|
||||||
list.push(`_Join:${field}:${schema.className}`);
|
list.push(`_Join:${field}:${schema.className}`);
|
||||||
}
|
}
|
||||||
@@ -343,7 +343,7 @@ const buildWhereClause = ({
|
|||||||
} else if (['$or', '$nor', '$and'].includes(fieldName)) {
|
} else if (['$or', '$nor', '$and'].includes(fieldName)) {
|
||||||
const clauses = [];
|
const clauses = [];
|
||||||
const clauseValues = [];
|
const clauseValues = [];
|
||||||
fieldValue.forEach((subQuery) => {
|
fieldValue.forEach(subQuery => {
|
||||||
const clause = buildWhereClause({
|
const clause = buildWhereClause({
|
||||||
schema,
|
schema,
|
||||||
query: subQuery,
|
query: subQuery,
|
||||||
@@ -490,13 +490,13 @@ const buildWhereClause = ({
|
|||||||
};
|
};
|
||||||
if (fieldValue.$in) {
|
if (fieldValue.$in) {
|
||||||
createConstraint(
|
createConstraint(
|
||||||
_.flatMap(fieldValue.$in, (elt) => elt),
|
_.flatMap(fieldValue.$in, elt => elt),
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (fieldValue.$nin) {
|
if (fieldValue.$nin) {
|
||||||
createConstraint(
|
createConstraint(
|
||||||
_.flatMap(fieldValue.$nin, (elt) => elt),
|
_.flatMap(fieldValue.$nin, elt => elt),
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -711,7 +711,7 @@ const buildWhereClause = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
points = points
|
points = points
|
||||||
.map((point) => {
|
.map(point => {
|
||||||
if (point instanceof Array && point.length === 2) {
|
if (point instanceof Array && point.length === 2) {
|
||||||
Parse.GeoPoint._validate(point[1], point[0]);
|
Parse.GeoPoint._validate(point[1], point[0]);
|
||||||
return `(${point[0]}, ${point[1]})`;
|
return `(${point[0]}, ${point[1]})`;
|
||||||
@@ -799,7 +799,7 @@ const buildWhereClause = ({
|
|||||||
index += 2;
|
index += 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.keys(ParseToPosgresComparator).forEach((cmp) => {
|
Object.keys(ParseToPosgresComparator).forEach(cmp => {
|
||||||
if (fieldValue[cmp] || fieldValue[cmp] === 0) {
|
if (fieldValue[cmp] || fieldValue[cmp] === 0) {
|
||||||
const pgComparator = ParseToPosgresComparator[cmp];
|
const pgComparator = ParseToPosgresComparator[cmp];
|
||||||
const postgresValue = toPostgresValue(fieldValue[cmp]);
|
const postgresValue = toPostgresValue(fieldValue[cmp]);
|
||||||
@@ -879,7 +879,7 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|||||||
.none(
|
.none(
|
||||||
'CREATE TABLE IF NOT EXISTS "_SCHEMA" ( "className" varChar(120), "schema" jsonb, "isParseClass" bool, PRIMARY KEY ("className") )'
|
'CREATE TABLE IF NOT EXISTS "_SCHEMA" ( "className" varChar(120), "schema" jsonb, "isParseClass" bool, PRIMARY KEY ("className") )'
|
||||||
)
|
)
|
||||||
.catch((error) => {
|
.catch(error => {
|
||||||
if (
|
if (
|
||||||
error.code === PostgresDuplicateRelationError ||
|
error.code === PostgresDuplicateRelationError ||
|
||||||
error.code === PostgresUniqueIndexViolationError ||
|
error.code === PostgresUniqueIndexViolationError ||
|
||||||
@@ -896,13 +896,13 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|||||||
return this._client.one(
|
return this._client.one(
|
||||||
'SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = $1)',
|
'SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = $1)',
|
||||||
[name],
|
[name],
|
||||||
(a) => a.exists
|
a => a.exists
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async setClassLevelPermissions(className: string, CLPs: any) {
|
async setClassLevelPermissions(className: string, CLPs: any) {
|
||||||
const self = this;
|
const self = this;
|
||||||
await this._client.task('set-class-level-permissions', async (t) => {
|
await this._client.task('set-class-level-permissions', async t => {
|
||||||
await self._ensureSchemaCollectionExists(t);
|
await self._ensureSchemaCollectionExists(t);
|
||||||
const values = [
|
const values = [
|
||||||
className,
|
className,
|
||||||
@@ -934,7 +934,7 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|||||||
}
|
}
|
||||||
const deletedIndexes = [];
|
const deletedIndexes = [];
|
||||||
const insertedIndexes = [];
|
const insertedIndexes = [];
|
||||||
Object.keys(submittedIndexes).forEach((name) => {
|
Object.keys(submittedIndexes).forEach(name => {
|
||||||
const field = submittedIndexes[name];
|
const field = submittedIndexes[name];
|
||||||
if (existingIndexes[name] && field.__op !== 'Delete') {
|
if (existingIndexes[name] && field.__op !== 'Delete') {
|
||||||
throw new Parse.Error(
|
throw new Parse.Error(
|
||||||
@@ -952,7 +952,7 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|||||||
deletedIndexes.push(name);
|
deletedIndexes.push(name);
|
||||||
delete existingIndexes[name];
|
delete existingIndexes[name];
|
||||||
} else {
|
} else {
|
||||||
Object.keys(field).forEach((key) => {
|
Object.keys(field).forEach(key => {
|
||||||
if (!Object.prototype.hasOwnProperty.call(fields, key)) {
|
if (!Object.prototype.hasOwnProperty.call(fields, key)) {
|
||||||
throw new Parse.Error(
|
throw new Parse.Error(
|
||||||
Parse.Error.INVALID_QUERY,
|
Parse.Error.INVALID_QUERY,
|
||||||
@@ -967,7 +967,7 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
await conn.tx('set-indexes-with-schema-format', async (t) => {
|
await conn.tx('set-indexes-with-schema-format', async t => {
|
||||||
if (insertedIndexes.length > 0) {
|
if (insertedIndexes.length > 0) {
|
||||||
await self.createIndexes(className, insertedIndexes, t);
|
await self.createIndexes(className, insertedIndexes, t);
|
||||||
}
|
}
|
||||||
@@ -985,7 +985,7 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|||||||
async createClass(className: string, schema: SchemaType, conn: ?any) {
|
async createClass(className: string, schema: SchemaType, conn: ?any) {
|
||||||
conn = conn || this._client;
|
conn = conn || this._client;
|
||||||
return conn
|
return conn
|
||||||
.tx('create-class', async (t) => {
|
.tx('create-class', async t => {
|
||||||
const q1 = this.createTable(className, schema, t);
|
const q1 = this.createTable(className, schema, t);
|
||||||
const q2 = t.none(
|
const q2 = t.none(
|
||||||
'INSERT INTO "_SCHEMA" ("className", "schema", "isParseClass") VALUES ($<className>, $<schema>, true)',
|
'INSERT INTO "_SCHEMA" ("className", "schema", "isParseClass") VALUES ($<className>, $<schema>, true)',
|
||||||
@@ -1005,7 +1005,7 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
return toParseSchema(schema);
|
return toParseSchema(schema);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch(err => {
|
||||||
if (err.data[0].result.code === PostgresTransactionAbortedError) {
|
if (err.data[0].result.code === PostgresTransactionAbortedError) {
|
||||||
err = err.data[1].result;
|
err = err.data[1].result;
|
||||||
}
|
}
|
||||||
@@ -1042,7 +1042,7 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|||||||
}
|
}
|
||||||
let index = 2;
|
let index = 2;
|
||||||
const relations = [];
|
const relations = [];
|
||||||
Object.keys(fields).forEach((fieldName) => {
|
Object.keys(fields).forEach(fieldName => {
|
||||||
const parseType = fields[fieldName];
|
const parseType = fields[fieldName];
|
||||||
// Skip when it's a relation
|
// Skip when it's a relation
|
||||||
// We'll create the tables later
|
// We'll create the tables later
|
||||||
@@ -1065,7 +1065,7 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|||||||
const values = [className, ...valuesArray];
|
const values = [className, ...valuesArray];
|
||||||
|
|
||||||
debug(qs, values);
|
debug(qs, values);
|
||||||
return conn.task('create-table', async (t) => {
|
return conn.task('create-table', async t => {
|
||||||
try {
|
try {
|
||||||
await self._ensureSchemaCollectionExists(t);
|
await self._ensureSchemaCollectionExists(t);
|
||||||
await t.none(qs, values);
|
await t.none(qs, values);
|
||||||
@@ -1075,9 +1075,9 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|||||||
}
|
}
|
||||||
// ELSE: Table already exists, must have been created by a different request. Ignore the error.
|
// ELSE: Table already exists, must have been created by a different request. Ignore the error.
|
||||||
}
|
}
|
||||||
await t.tx('create-table-tx', (tx) => {
|
await t.tx('create-table-tx', tx => {
|
||||||
return tx.batch(
|
return tx.batch(
|
||||||
relations.map((fieldName) => {
|
relations.map(fieldName => {
|
||||||
return tx.none(
|
return tx.none(
|
||||||
'CREATE TABLE IF NOT EXISTS $<joinTable:name> ("relatedId" varChar(120), "owningId" varChar(120), PRIMARY KEY("relatedId", "owningId") )',
|
'CREATE TABLE IF NOT EXISTS $<joinTable:name> ("relatedId" varChar(120), "owningId" varChar(120), PRIMARY KEY("relatedId", "owningId") )',
|
||||||
{ joinTable: `_Join:${fieldName}:${className}` }
|
{ joinTable: `_Join:${fieldName}:${className}` }
|
||||||
@@ -1093,15 +1093,15 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|||||||
conn = conn || this._client;
|
conn = conn || this._client;
|
||||||
const self = this;
|
const self = this;
|
||||||
|
|
||||||
await conn.tx('schema-upgrade', async (t) => {
|
await conn.tx('schema-upgrade', async t => {
|
||||||
const columns = await t.map(
|
const columns = await t.map(
|
||||||
'SELECT column_name FROM information_schema.columns WHERE table_name = $<className>',
|
'SELECT column_name FROM information_schema.columns WHERE table_name = $<className>',
|
||||||
{ className },
|
{ className },
|
||||||
(a) => a.column_name
|
a => a.column_name
|
||||||
);
|
);
|
||||||
const newColumns = Object.keys(schema.fields)
|
const newColumns = Object.keys(schema.fields)
|
||||||
.filter((item) => columns.indexOf(item) === -1)
|
.filter(item => columns.indexOf(item) === -1)
|
||||||
.map((fieldName) =>
|
.map(fieldName =>
|
||||||
self.addFieldIfNotExists(
|
self.addFieldIfNotExists(
|
||||||
className,
|
className,
|
||||||
fieldName,
|
fieldName,
|
||||||
@@ -1124,7 +1124,7 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|||||||
debug('addFieldIfNotExists', { className, fieldName, type });
|
debug('addFieldIfNotExists', { className, fieldName, type });
|
||||||
conn = conn || this._client;
|
conn = conn || this._client;
|
||||||
const self = this;
|
const self = this;
|
||||||
await conn.tx('add-field-if-not-exists', async (t) => {
|
await conn.tx('add-field-if-not-exists', async t => {
|
||||||
if (type.type !== 'Relation') {
|
if (type.type !== 'Relation') {
|
||||||
try {
|
try {
|
||||||
await t.none(
|
await t.none(
|
||||||
@@ -1183,7 +1183,7 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
return this._client
|
return this._client
|
||||||
.tx((t) => t.none(this._pgp.helpers.concat(operations)))
|
.tx(t => t.none(this._pgp.helpers.concat(operations)))
|
||||||
.then(() => className.indexOf('_Join:') != 0); // resolves with false when _Join table
|
.then(() => className.indexOf('_Join:') != 0); // resolves with false when _Join table
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1194,7 +1194,7 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|||||||
debug('deleteAllClasses');
|
debug('deleteAllClasses');
|
||||||
|
|
||||||
await this._client
|
await this._client
|
||||||
.task('delete-all-classes', async (t) => {
|
.task('delete-all-classes', async t => {
|
||||||
try {
|
try {
|
||||||
const results = await t.any('SELECT * FROM "_SCHEMA"');
|
const results = await t.any('SELECT * FROM "_SCHEMA"');
|
||||||
const joins = results.reduce((list: Array<string>, schema: any) => {
|
const joins = results.reduce((list: Array<string>, schema: any) => {
|
||||||
@@ -1210,14 +1210,14 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|||||||
'_GraphQLConfig',
|
'_GraphQLConfig',
|
||||||
'_Audience',
|
'_Audience',
|
||||||
'_Idempotency',
|
'_Idempotency',
|
||||||
...results.map((result) => result.className),
|
...results.map(result => result.className),
|
||||||
...joins,
|
...joins,
|
||||||
];
|
];
|
||||||
const queries = classes.map((className) => ({
|
const queries = classes.map(className => ({
|
||||||
query: 'DROP TABLE IF EXISTS $<className:name>',
|
query: 'DROP TABLE IF EXISTS $<className:name>',
|
||||||
values: { className },
|
values: { className },
|
||||||
}));
|
}));
|
||||||
await t.tx((tx) => tx.none(helpers.concat(queries)));
|
await t.tx(tx => tx.none(helpers.concat(queries)));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.code !== PostgresRelationDoesNotExistError) {
|
if (error.code !== PostgresRelationDoesNotExistError) {
|
||||||
throw error;
|
throw error;
|
||||||
@@ -1265,7 +1265,7 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|||||||
})
|
})
|
||||||
.join(', DROP COLUMN');
|
.join(', DROP COLUMN');
|
||||||
|
|
||||||
await this._client.tx('delete-fields', async (t) => {
|
await this._client.tx('delete-fields', async t => {
|
||||||
await t.none(
|
await t.none(
|
||||||
'UPDATE "_SCHEMA" SET "schema" = $<schema> WHERE "className" = $<className>',
|
'UPDATE "_SCHEMA" SET "schema" = $<schema> WHERE "className" = $<className>',
|
||||||
{ schema, className }
|
{ schema, className }
|
||||||
@@ -1281,9 +1281,9 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|||||||
// rejection reason are TBD.
|
// rejection reason are TBD.
|
||||||
async getAllClasses() {
|
async getAllClasses() {
|
||||||
const self = this;
|
const self = this;
|
||||||
return this._client.task('get-all-classes', async (t) => {
|
return this._client.task('get-all-classes', async t => {
|
||||||
await self._ensureSchemaCollectionExists(t);
|
await self._ensureSchemaCollectionExists(t);
|
||||||
return await t.map('SELECT * FROM "_SCHEMA"', null, (row) =>
|
return await t.map('SELECT * FROM "_SCHEMA"', null, row =>
|
||||||
toParseSchema({ className: row.className, ...row.schema })
|
toParseSchema({ className: row.className, ...row.schema })
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -1298,7 +1298,7 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|||||||
.any('SELECT * FROM "_SCHEMA" WHERE "className" = $<className>', {
|
.any('SELECT * FROM "_SCHEMA" WHERE "className" = $<className>', {
|
||||||
className,
|
className,
|
||||||
})
|
})
|
||||||
.then((result) => {
|
.then(result => {
|
||||||
if (result.length !== 1) {
|
if (result.length !== 1) {
|
||||||
throw undefined;
|
throw undefined;
|
||||||
}
|
}
|
||||||
@@ -1324,7 +1324,7 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|||||||
|
|
||||||
validateKeys(object);
|
validateKeys(object);
|
||||||
|
|
||||||
Object.keys(object).forEach((fieldName) => {
|
Object.keys(object).forEach(fieldName => {
|
||||||
if (object[fieldName] === null) {
|
if (object[fieldName] === null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1426,7 +1426,7 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|||||||
}
|
}
|
||||||
return `$${index + 2 + columnsArray.length}${termination}`;
|
return `$${index + 2 + columnsArray.length}${termination}`;
|
||||||
});
|
});
|
||||||
const geoPointsInjects = Object.keys(geoPoints).map((key) => {
|
const geoPointsInjects = Object.keys(geoPoints).map(key => {
|
||||||
const value = geoPoints[key];
|
const value = geoPoints[key];
|
||||||
valuesArray.push(value.longitude, value.latitude);
|
valuesArray.push(value.longitude, value.latitude);
|
||||||
const l = valuesArray.length + columnsArray.length;
|
const l = valuesArray.length + columnsArray.length;
|
||||||
@@ -1447,7 +1447,7 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|||||||
)
|
)
|
||||||
.none(qs, values)
|
.none(qs, values)
|
||||||
.then(() => ({ ops: [object] }))
|
.then(() => ({ ops: [object] }))
|
||||||
.catch((error) => {
|
.catch(error => {
|
||||||
if (error.code === PostgresUniqueIndexViolationError) {
|
if (error.code === PostgresUniqueIndexViolationError) {
|
||||||
const err = new Parse.Error(
|
const err = new Parse.Error(
|
||||||
Parse.Error.DUPLICATE_VALUE,
|
Parse.Error.DUPLICATE_VALUE,
|
||||||
@@ -1498,8 +1498,8 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|||||||
? transactionalSession.t
|
? transactionalSession.t
|
||||||
: this._client
|
: this._client
|
||||||
)
|
)
|
||||||
.one(qs, values, (a) => +a.count)
|
.one(qs, values, a => +a.count)
|
||||||
.then((count) => {
|
.then(count => {
|
||||||
if (count === 0) {
|
if (count === 0) {
|
||||||
throw new Parse.Error(
|
throw new Parse.Error(
|
||||||
Parse.Error.OBJECT_NOT_FOUND,
|
Parse.Error.OBJECT_NOT_FOUND,
|
||||||
@@ -1509,7 +1509,7 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch(error => {
|
||||||
if (error.code !== PostgresRelationDoesNotExistError) {
|
if (error.code !== PostgresRelationDoesNotExistError) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
@@ -1535,7 +1535,7 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|||||||
query,
|
query,
|
||||||
update,
|
update,
|
||||||
transactionalSession
|
transactionalSession
|
||||||
).then((val) => val[0]);
|
).then(val => val[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply the update to all objects that match the given Parse Query.
|
// Apply the update to all objects that match the given Parse Query.
|
||||||
@@ -1556,7 +1556,7 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|||||||
|
|
||||||
// Set flag for dot notation fields
|
// Set flag for dot notation fields
|
||||||
const dotNotationOptions = {};
|
const dotNotationOptions = {};
|
||||||
Object.keys(update).forEach((fieldName) => {
|
Object.keys(update).forEach(fieldName => {
|
||||||
if (fieldName.indexOf('.') > -1) {
|
if (fieldName.indexOf('.') > -1) {
|
||||||
const components = fieldName.split('.');
|
const components = fieldName.split('.');
|
||||||
const first = components.shift();
|
const first = components.shift();
|
||||||
@@ -1707,7 +1707,7 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|||||||
) {
|
) {
|
||||||
// Gather keys to increment
|
// Gather keys to increment
|
||||||
const keysToIncrement = Object.keys(originalUpdate)
|
const keysToIncrement = Object.keys(originalUpdate)
|
||||||
.filter((k) => {
|
.filter(k => {
|
||||||
// choose top level fields that have a delete operation set
|
// choose top level fields that have a delete operation set
|
||||||
// Note that Object.keys is iterating over the **original** update object
|
// Note that Object.keys is iterating over the **original** update object
|
||||||
// and that some of the keys of the original update could be null or undefined:
|
// and that some of the keys of the original update could be null or undefined:
|
||||||
@@ -1720,26 +1720,26 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|||||||
k.split('.')[0] === fieldName
|
k.split('.')[0] === fieldName
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
.map((k) => k.split('.')[1]);
|
.map(k => k.split('.')[1]);
|
||||||
|
|
||||||
let incrementPatterns = '';
|
let incrementPatterns = '';
|
||||||
if (keysToIncrement.length > 0) {
|
if (keysToIncrement.length > 0) {
|
||||||
incrementPatterns =
|
incrementPatterns =
|
||||||
' || ' +
|
' || ' +
|
||||||
keysToIncrement
|
keysToIncrement
|
||||||
.map((c) => {
|
.map(c => {
|
||||||
const amount = fieldValue[c].amount;
|
const amount = fieldValue[c].amount;
|
||||||
return `CONCAT('{"${c}":', COALESCE($${index}:name->>'${c}','0')::int + ${amount}, '}')::jsonb`;
|
return `CONCAT('{"${c}":', COALESCE($${index}:name->>'${c}','0')::int + ${amount}, '}')::jsonb`;
|
||||||
})
|
})
|
||||||
.join(' || ');
|
.join(' || ');
|
||||||
// Strip the keys
|
// Strip the keys
|
||||||
keysToIncrement.forEach((key) => {
|
keysToIncrement.forEach(key => {
|
||||||
delete fieldValue[key];
|
delete fieldValue[key];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const keysToDelete: Array<string> = Object.keys(originalUpdate)
|
const keysToDelete: Array<string> = Object.keys(originalUpdate)
|
||||||
.filter((k) => {
|
.filter(k => {
|
||||||
// choose top level fields that have a delete operation set.
|
// choose top level fields that have a delete operation set.
|
||||||
const value = originalUpdate[k];
|
const value = originalUpdate[k];
|
||||||
return (
|
return (
|
||||||
@@ -1749,7 +1749,7 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|||||||
k.split('.')[0] === fieldName
|
k.split('.')[0] === fieldName
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
.map((k) => k.split('.')[1]);
|
.map(k => k.split('.')[1]);
|
||||||
|
|
||||||
const deletePatterns = keysToDelete.reduce(
|
const deletePatterns = keysToDelete.reduce(
|
||||||
(p: string, c: string, i: number) => {
|
(p: string, c: string, i: number) => {
|
||||||
@@ -1834,7 +1834,7 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|||||||
schema,
|
schema,
|
||||||
createValue,
|
createValue,
|
||||||
transactionalSession
|
transactionalSession
|
||||||
).catch((error) => {
|
).catch(error => {
|
||||||
// ignore duplicate value errors as it's upsert
|
// ignore duplicate value errors as it's upsert
|
||||||
if (error.code !== Parse.Error.DUPLICATE_VALUE) {
|
if (error.code !== Parse.Error.DUPLICATE_VALUE) {
|
||||||
throw error;
|
throw error;
|
||||||
@@ -1889,7 +1889,7 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|||||||
if (sort) {
|
if (sort) {
|
||||||
const sortCopy: any = sort;
|
const sortCopy: any = sort;
|
||||||
const sorting = Object.keys(sort)
|
const sorting = Object.keys(sort)
|
||||||
.map((key) => {
|
.map(key => {
|
||||||
const transformKey = transformDotFieldToComponents(key).join('->');
|
const transformKey = transformDotFieldToComponents(key).join('->');
|
||||||
// Using $idx pattern gives: non-integer constant in ORDER BY
|
// Using $idx pattern gives: non-integer constant in ORDER BY
|
||||||
if (sortCopy[key] === 1) {
|
if (sortCopy[key] === 1) {
|
||||||
@@ -1938,18 +1938,18 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|||||||
debug(qs, values);
|
debug(qs, values);
|
||||||
return this._client
|
return this._client
|
||||||
.any(qs, values)
|
.any(qs, values)
|
||||||
.catch((error) => {
|
.catch(error => {
|
||||||
// Query on non existing table, don't crash
|
// Query on non existing table, don't crash
|
||||||
if (error.code !== PostgresRelationDoesNotExistError) {
|
if (error.code !== PostgresRelationDoesNotExistError) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
})
|
})
|
||||||
.then((results) => {
|
.then(results => {
|
||||||
if (explain) {
|
if (explain) {
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
return results.map((object) =>
|
return results.map(object =>
|
||||||
this.postgresObjectToParseObject(className, object, schema)
|
this.postgresObjectToParseObject(className, object, schema)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -1958,7 +1958,7 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|||||||
// Converts from a postgres-format object to a REST-format object.
|
// Converts from a postgres-format object to a REST-format object.
|
||||||
// Does not strip out anything based on a lack of authentication.
|
// Does not strip out anything based on a lack of authentication.
|
||||||
postgresObjectToParseObject(className: string, object: any, schema: any) {
|
postgresObjectToParseObject(className: string, object: any, schema: any) {
|
||||||
Object.keys(schema.fields).forEach((fieldName) => {
|
Object.keys(schema.fields).forEach(fieldName => {
|
||||||
if (schema.fields[fieldName].type === 'Pointer' && object[fieldName]) {
|
if (schema.fields[fieldName].type === 'Pointer' && object[fieldName]) {
|
||||||
object[fieldName] = {
|
object[fieldName] = {
|
||||||
objectId: object[fieldName],
|
objectId: object[fieldName],
|
||||||
@@ -1982,7 +1982,7 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|||||||
if (object[fieldName] && schema.fields[fieldName].type === 'Polygon') {
|
if (object[fieldName] && schema.fields[fieldName].type === 'Polygon') {
|
||||||
let coords = object[fieldName];
|
let coords = object[fieldName];
|
||||||
coords = coords.substr(2, coords.length - 4).split('),(');
|
coords = coords.substr(2, coords.length - 4).split('),(');
|
||||||
coords = coords.map((point) => {
|
coords = coords.map(point => {
|
||||||
return [
|
return [
|
||||||
parseFloat(point.split(',')[1]),
|
parseFloat(point.split(',')[1]),
|
||||||
parseFloat(point.split(',')[0]),
|
parseFloat(point.split(',')[0]),
|
||||||
@@ -2072,7 +2072,7 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|||||||
const qs = `ALTER TABLE $1:name ADD CONSTRAINT $2:name UNIQUE (${constraintPatterns.join()})`;
|
const qs = `ALTER TABLE $1:name ADD CONSTRAINT $2:name UNIQUE (${constraintPatterns.join()})`;
|
||||||
return this._client
|
return this._client
|
||||||
.none(qs, [className, constraintName, ...fieldNames])
|
.none(qs, [className, constraintName, ...fieldNames])
|
||||||
.catch((error) => {
|
.catch(error => {
|
||||||
if (
|
if (
|
||||||
error.code === PostgresDuplicateRelationError &&
|
error.code === PostgresDuplicateRelationError &&
|
||||||
error.message.includes(constraintName)
|
error.message.includes(constraintName)
|
||||||
@@ -2123,14 +2123,14 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return this._client
|
return this._client
|
||||||
.one(qs, values, (a) => {
|
.one(qs, values, a => {
|
||||||
if (a.approximate_row_count != null) {
|
if (a.approximate_row_count != null) {
|
||||||
return +a.approximate_row_count;
|
return +a.approximate_row_count;
|
||||||
} else {
|
} else {
|
||||||
return +a.count;
|
return +a.count;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch(error => {
|
||||||
if (error.code !== PostgresRelationDoesNotExistError) {
|
if (error.code !== PostgresRelationDoesNotExistError) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
@@ -2179,16 +2179,16 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|||||||
debug(qs, values);
|
debug(qs, values);
|
||||||
return this._client
|
return this._client
|
||||||
.any(qs, values)
|
.any(qs, values)
|
||||||
.catch((error) => {
|
.catch(error => {
|
||||||
if (error.code === PostgresMissingColumnError) {
|
if (error.code === PostgresMissingColumnError) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
throw error;
|
throw error;
|
||||||
})
|
})
|
||||||
.then((results) => {
|
.then(results => {
|
||||||
if (!isNested) {
|
if (!isNested) {
|
||||||
results = results.filter((object) => object[field] !== null);
|
results = results.filter(object => object[field] !== null);
|
||||||
return results.map((object) => {
|
return results.map(object => {
|
||||||
if (!isPointerField) {
|
if (!isPointerField) {
|
||||||
return object[field];
|
return object[field];
|
||||||
}
|
}
|
||||||
@@ -2200,10 +2200,10 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
const child = fieldName.split('.')[1];
|
const child = fieldName.split('.')[1];
|
||||||
return results.map((object) => object[column][child]);
|
return results.map(object => object[column][child]);
|
||||||
})
|
})
|
||||||
.then((results) =>
|
.then(results =>
|
||||||
results.map((object) =>
|
results.map(object =>
|
||||||
this.postgresObjectToParseObject(className, object, schema)
|
this.postgresObjectToParseObject(className, object, schema)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@@ -2340,7 +2340,7 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|||||||
|
|
||||||
if (stage.$match.$or) {
|
if (stage.$match.$or) {
|
||||||
const collapse = {};
|
const collapse = {};
|
||||||
stage.$match.$or.forEach((element) => {
|
stage.$match.$or.forEach(element => {
|
||||||
for (const key in element) {
|
for (const key in element) {
|
||||||
collapse[key] = element[key];
|
collapse[key] = element[key];
|
||||||
}
|
}
|
||||||
@@ -2350,7 +2350,7 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|||||||
for (const field in stage.$match) {
|
for (const field in stage.$match) {
|
||||||
const value = stage.$match[field];
|
const value = stage.$match[field];
|
||||||
const matchPatterns = [];
|
const matchPatterns = [];
|
||||||
Object.keys(ParseToPosgresComparator).forEach((cmp) => {
|
Object.keys(ParseToPosgresComparator).forEach(cmp => {
|
||||||
if (value[cmp]) {
|
if (value[cmp]) {
|
||||||
const pgComparator = ParseToPosgresComparator[cmp];
|
const pgComparator = ParseToPosgresComparator[cmp];
|
||||||
matchPatterns.push(
|
matchPatterns.push(
|
||||||
@@ -2390,7 +2390,7 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|||||||
const sort = stage.$sort;
|
const sort = stage.$sort;
|
||||||
const keys = Object.keys(sort);
|
const keys = Object.keys(sort);
|
||||||
const sorting = keys
|
const sorting = keys
|
||||||
.map((key) => {
|
.map(key => {
|
||||||
const transformer = sort[key] === 1 ? 'ASC' : 'DESC';
|
const transformer = sort[key] === 1 ? 'ASC' : 'DESC';
|
||||||
const order = `$${index}:name ${transformer}`;
|
const order = `$${index}:name ${transformer}`;
|
||||||
index += 1;
|
index += 1;
|
||||||
@@ -2418,14 +2418,14 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|||||||
? this.createExplainableQuery(originalQuery)
|
? this.createExplainableQuery(originalQuery)
|
||||||
: originalQuery;
|
: originalQuery;
|
||||||
debug(qs, values);
|
debug(qs, values);
|
||||||
return this._client.any(qs, values).then((a) => {
|
return this._client.any(qs, values).then(a => {
|
||||||
if (explain) {
|
if (explain) {
|
||||||
return a;
|
return a;
|
||||||
}
|
}
|
||||||
const results = a.map((object) =>
|
const results = a.map(object =>
|
||||||
this.postgresObjectToParseObject(className, object, schema)
|
this.postgresObjectToParseObject(className, object, schema)
|
||||||
);
|
);
|
||||||
results.forEach((result) => {
|
results.forEach(result => {
|
||||||
if (!Object.prototype.hasOwnProperty.call(result, 'objectId')) {
|
if (!Object.prototype.hasOwnProperty.call(result, 'objectId')) {
|
||||||
result.objectId = null;
|
result.objectId = null;
|
||||||
}
|
}
|
||||||
@@ -2447,9 +2447,9 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|||||||
async performInitialization({ VolatileClassesSchemas }: any) {
|
async performInitialization({ VolatileClassesSchemas }: any) {
|
||||||
// TODO: This method needs to be rewritten to make proper use of connections (@vitaly-t)
|
// TODO: This method needs to be rewritten to make proper use of connections (@vitaly-t)
|
||||||
debug('performInitialization');
|
debug('performInitialization');
|
||||||
const promises = VolatileClassesSchemas.map((schema) => {
|
const promises = VolatileClassesSchemas.map(schema => {
|
||||||
return this.createTable(schema.className, schema)
|
return this.createTable(schema.className, schema)
|
||||||
.catch((err) => {
|
.catch(err => {
|
||||||
if (
|
if (
|
||||||
err.code === PostgresDuplicateRelationError ||
|
err.code === PostgresDuplicateRelationError ||
|
||||||
err.code === Parse.Error.INVALID_CLASS_NAME
|
err.code === Parse.Error.INVALID_CLASS_NAME
|
||||||
@@ -2462,7 +2462,7 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|||||||
});
|
});
|
||||||
return Promise.all(promises)
|
return Promise.all(promises)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
return this._client.tx('perform-initialization', (t) => {
|
return this._client.tx('perform-initialization', t => {
|
||||||
return t.batch([
|
return t.batch([
|
||||||
t.none(sql.misc.jsonObjectSetKeys),
|
t.none(sql.misc.jsonObjectSetKeys),
|
||||||
t.none(sql.array.add),
|
t.none(sql.array.add),
|
||||||
@@ -2474,10 +2474,10 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.then((data) => {
|
.then(data => {
|
||||||
debug(`initializationDone in ${data.duration}`);
|
debug(`initializationDone in ${data.duration}`);
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch(error => {
|
||||||
/* eslint-disable no-console */
|
/* eslint-disable no-console */
|
||||||
console.error(error);
|
console.error(error);
|
||||||
});
|
});
|
||||||
@@ -2488,9 +2488,9 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|||||||
indexes: any,
|
indexes: any,
|
||||||
conn: ?any
|
conn: ?any
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
return (conn || this._client).tx((t) =>
|
return (conn || this._client).tx(t =>
|
||||||
t.batch(
|
t.batch(
|
||||||
indexes.map((i) => {
|
indexes.map(i => {
|
||||||
return t.none('CREATE INDEX $1:name ON $2:name ($3:name)', [
|
return t.none('CREATE INDEX $1:name ON $2:name ($3:name)', [
|
||||||
i.name,
|
i.name,
|
||||||
className,
|
className,
|
||||||
@@ -2517,11 +2517,11 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async dropIndexes(className: string, indexes: any, conn: any): Promise<void> {
|
async dropIndexes(className: string, indexes: any, conn: any): Promise<void> {
|
||||||
const queries = indexes.map((i) => ({
|
const queries = indexes.map(i => ({
|
||||||
query: 'DROP INDEX $1:name',
|
query: 'DROP INDEX $1:name',
|
||||||
values: i,
|
values: i,
|
||||||
}));
|
}));
|
||||||
await (conn || this._client).tx((t) =>
|
await (conn || this._client).tx(t =>
|
||||||
t.none(this._pgp.helpers.concat(queries))
|
t.none(this._pgp.helpers.concat(queries))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -2541,11 +2541,11 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async createTransactionalSession(): Promise<any> {
|
async createTransactionalSession(): Promise<any> {
|
||||||
return new Promise((resolve) => {
|
return new Promise(resolve => {
|
||||||
const transactionalSession = {};
|
const transactionalSession = {};
|
||||||
transactionalSession.result = this._client.tx((t) => {
|
transactionalSession.result = this._client.tx(t => {
|
||||||
transactionalSession.t = t;
|
transactionalSession.t = t;
|
||||||
transactionalSession.promise = new Promise((resolve) => {
|
transactionalSession.promise = new Promise(resolve => {
|
||||||
transactionalSession.resolve = resolve;
|
transactionalSession.resolve = resolve;
|
||||||
});
|
});
|
||||||
transactionalSession.batch = [];
|
transactionalSession.batch = [];
|
||||||
@@ -2577,7 +2577,7 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|||||||
fieldNames: string[],
|
fieldNames: string[],
|
||||||
indexName: ?string,
|
indexName: ?string,
|
||||||
caseInsensitive: boolean = false,
|
caseInsensitive: boolean = false,
|
||||||
options?: Object = {},
|
options?: Object = {}
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
const conn = options.conn !== undefined ? options.conn : this._client;
|
const conn = options.conn !== undefined ? options.conn : this._client;
|
||||||
const defaultIndexName = `parse_default_${fieldNames.sort().join('_')}`;
|
const defaultIndexName = `parse_default_${fieldNames.sort().join('_')}`;
|
||||||
@@ -2591,7 +2591,7 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
|||||||
const qs = `CREATE INDEX $1:name ON $2:name (${constraintPatterns.join()})`;
|
const qs = `CREATE INDEX $1:name ON $2:name (${constraintPatterns.join()})`;
|
||||||
await conn
|
await conn
|
||||||
.none(qs, [indexNameOptions.name, className, ...fieldNames])
|
.none(qs, [indexNameOptions.name, className, ...fieldNames])
|
||||||
.catch((error) => {
|
.catch(error => {
|
||||||
if (
|
if (
|
||||||
error.code === PostgresDuplicateRelationError &&
|
error.code === PostgresDuplicateRelationError &&
|
||||||
error.message.includes(indexNameOptions.name)
|
error.message.includes(indexNameOptions.name)
|
||||||
@@ -2644,7 +2644,7 @@ function convertPolygonToSQL(polygon) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
const points = polygon
|
const points = polygon
|
||||||
.map((point) => {
|
.map(point => {
|
||||||
Parse.GeoPoint._validate(parseFloat(point[1]), parseFloat(point[0]));
|
Parse.GeoPoint._validate(parseFloat(point[1]), parseFloat(point[0]));
|
||||||
return `(${point[1]}, ${point[0]})`;
|
return `(${point[1]}, ${point[0]})`;
|
||||||
})
|
})
|
||||||
@@ -2721,7 +2721,7 @@ function isAnyValueRegexStartsWith(values) {
|
|||||||
function createLiteralRegex(remaining) {
|
function createLiteralRegex(remaining) {
|
||||||
return remaining
|
return remaining
|
||||||
.split('')
|
.split('')
|
||||||
.map((c) => {
|
.map(c => {
|
||||||
const regex = RegExp('[0-9 ]|\\p{L}', 'u'); // Support all unicode letter chars
|
const regex = RegExp('[0-9 ]|\\p{L}', 'u'); // Support all unicode letter chars
|
||||||
if (c.match(regex) !== null) {
|
if (c.match(regex) !== null) {
|
||||||
// don't escape alphanumeric characters
|
// don't escape alphanumeric characters
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ export interface StorageAdapter {
|
|||||||
fieldNames: string[],
|
fieldNames: string[],
|
||||||
indexName?: string,
|
indexName?: string,
|
||||||
caseSensitive?: boolean,
|
caseSensitive?: boolean,
|
||||||
options?: Object,
|
options?: Object
|
||||||
): Promise<any>;
|
): Promise<any>;
|
||||||
ensureUniqueness(
|
ensureUniqueness(
|
||||||
className: string,
|
className: string,
|
||||||
|
|||||||
@@ -114,7 +114,9 @@ export class Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static validateIdempotencyOptions(idempotencyOptions) {
|
static validateIdempotencyOptions(idempotencyOptions) {
|
||||||
if (!idempotencyOptions) { return; }
|
if (!idempotencyOptions) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (idempotencyOptions.ttl === undefined) {
|
if (idempotencyOptions.ttl === undefined) {
|
||||||
idempotencyOptions.ttl = IdempotencyOptions.ttl.default;
|
idempotencyOptions.ttl = IdempotencyOptions.ttl.default;
|
||||||
} else if (!isNaN(idempotencyOptions.ttl) && idempotencyOptions.ttl <= 0) {
|
} else if (!isNaN(idempotencyOptions.ttl) && idempotencyOptions.ttl <= 0) {
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ const defaultColumns: { [string]: SchemaFields } = Object.freeze({
|
|||||||
_Idempotency: {
|
_Idempotency: {
|
||||||
reqId: { type: 'String' },
|
reqId: { type: 'String' },
|
||||||
expire: { type: 'Date' },
|
expire: { type: 'Date' },
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const requiredColumns = Object.freeze({
|
const requiredColumns = Object.freeze({
|
||||||
@@ -165,7 +165,7 @@ const systemClasses = Object.freeze([
|
|||||||
'_JobStatus',
|
'_JobStatus',
|
||||||
'_JobSchedule',
|
'_JobSchedule',
|
||||||
'_Audience',
|
'_Audience',
|
||||||
'_Idempotency'
|
'_Idempotency',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const volatileClasses = Object.freeze([
|
const volatileClasses = Object.freeze([
|
||||||
@@ -176,7 +176,7 @@ const volatileClasses = Object.freeze([
|
|||||||
'_GraphQLConfig',
|
'_GraphQLConfig',
|
||||||
'_JobSchedule',
|
'_JobSchedule',
|
||||||
'_Audience',
|
'_Audience',
|
||||||
'_Idempotency'
|
'_Idempotency',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Anything that start with role
|
// Anything that start with role
|
||||||
@@ -681,7 +681,7 @@ const VolatileClassesSchemas = [
|
|||||||
_GlobalConfigSchema,
|
_GlobalConfigSchema,
|
||||||
_GraphQLConfigSchema,
|
_GraphQLConfigSchema,
|
||||||
_AudienceSchema,
|
_AudienceSchema,
|
||||||
_IdempotencySchema
|
_IdempotencySchema,
|
||||||
];
|
];
|
||||||
|
|
||||||
const dbTypeMatchesObjectType = (
|
const dbTypeMatchesObjectType = (
|
||||||
|
|||||||
@@ -5,8 +5,16 @@ const createObject = async (className, fields, config, auth, info) => {
|
|||||||
fields = {};
|
fields = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
return (await rest.create(config, auth, className, fields, info.clientSDK, info.context))
|
return (
|
||||||
.response;
|
await rest.create(
|
||||||
|
config,
|
||||||
|
auth,
|
||||||
|
className,
|
||||||
|
fields,
|
||||||
|
info.clientSDK,
|
||||||
|
info.context
|
||||||
|
)
|
||||||
|
).response;
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateObject = async (
|
const updateObject = async (
|
||||||
@@ -21,7 +29,8 @@ const updateObject = async (
|
|||||||
fields = {};
|
fields = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
return (await rest.update(
|
return (
|
||||||
|
await rest.update(
|
||||||
config,
|
config,
|
||||||
auth,
|
auth,
|
||||||
className,
|
className,
|
||||||
@@ -29,7 +38,8 @@ const updateObject = async (
|
|||||||
fields,
|
fields,
|
||||||
info.clientSDK,
|
info.clientSDK,
|
||||||
info.context
|
info.context
|
||||||
)).response;
|
)
|
||||||
|
).response;
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteObject = async (className, objectId, config, auth, info) => {
|
const deleteObject = async (className, objectId, config, auth, info) => {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { transformQueryInputToParse } from '../transformers/query';
|
|||||||
/* eslint-disable*/
|
/* eslint-disable*/
|
||||||
const needToGetAllKeys = (fields, keys, parseClasses) =>
|
const needToGetAllKeys = (fields, keys, parseClasses) =>
|
||||||
keys
|
keys
|
||||||
? keys.split(',').some((keyName) => {
|
? keys.split(',').some(keyName => {
|
||||||
const key = keyName.split('.');
|
const key = keyName.split('.');
|
||||||
if (fields[key[0]]) {
|
if (fields[key[0]]) {
|
||||||
if (fields[key[0]].type === 'Pointer') {
|
if (fields[key[0]].type === 'Pointer') {
|
||||||
@@ -19,7 +19,11 @@ const needToGetAllKeys = (fields, keys, parseClasses) =>
|
|||||||
// Current sub key is not custom
|
// Current sub key is not custom
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else if (!key[1]) {
|
} else if (
|
||||||
|
!key[1] ||
|
||||||
|
fields[key[0]].type === 'Array' ||
|
||||||
|
fields[key[0]].type === 'Object'
|
||||||
|
) {
|
||||||
// current key is not custom
|
// current key is not custom
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -156,7 +160,7 @@ const findObjects = async (
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
selectedFields.find(
|
selectedFields.find(
|
||||||
(field) => field.startsWith('edges.') || field.startsWith('pageInfo.')
|
field => field.startsWith('edges.') || field.startsWith('pageInfo.')
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
if (limit || limit === 0) {
|
if (limit || limit === 0) {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import {
|
|||||||
GraphQLUnionType,
|
GraphQLUnionType,
|
||||||
} from 'graphql';
|
} from 'graphql';
|
||||||
import { toGlobalId } from 'graphql-relay';
|
import { toGlobalId } from 'graphql-relay';
|
||||||
import { GraphQLUpload } from 'graphql-upload';
|
import { GraphQLUpload } from '@graphql-tools/links';
|
||||||
|
|
||||||
class TypeValidationError extends Error {
|
class TypeValidationError extends Error {
|
||||||
constructor(value, type) {
|
constructor(value, type) {
|
||||||
@@ -162,7 +162,7 @@ const serializeDateIso = (value) => {
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
if (value instanceof Date) {
|
if (value instanceof Date) {
|
||||||
return value.toUTCString();
|
return value.toISOString();
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new TypeValidationError(value, 'Date');
|
throw new TypeValidationError(value, 'Date');
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { GraphQLNonNull } from 'graphql';
|
import { GraphQLNonNull } from 'graphql';
|
||||||
import { mutationWithClientMutationId } from 'graphql-relay';
|
import { mutationWithClientMutationId } from 'graphql-relay';
|
||||||
import { GraphQLUpload } from 'graphql-upload';
|
import { GraphQLUpload } from '@graphql-tools/links';
|
||||||
import Parse from 'parse/node';
|
import Parse from 'parse/node';
|
||||||
import * as defaultGraphQLTypes from './defaultGraphQLTypes';
|
import * as defaultGraphQLTypes from './defaultGraphQLTypes';
|
||||||
import logger from '../../logger';
|
import logger from '../../logger';
|
||||||
@@ -14,7 +14,7 @@ const handleUpload = async (upload, config) => {
|
|||||||
const chunks = [];
|
const chunks = [];
|
||||||
stream
|
stream
|
||||||
.on('error', reject)
|
.on('error', reject)
|
||||||
.on('data', chunk => chunks.push(chunk))
|
.on('data', (chunk) => chunks.push(chunk))
|
||||||
.on('end', () => resolve(Buffer.concat(chunks)));
|
.on('end', () => resolve(Buffer.concat(chunks)));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -52,7 +52,7 @@ const handleUpload = async (upload, config) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const load = parseGraphQLSchema => {
|
const load = (parseGraphQLSchema) => {
|
||||||
const createMutation = mutationWithClientMutationId({
|
const createMutation = mutationWithClientMutationId({
|
||||||
name: 'CreateFile',
|
name: 'CreateFile',
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -22,13 +22,12 @@ export function toGraphQLError(error) {
|
|||||||
return new ApolloError(message, code);
|
return new ApolloError(message, code);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const extractKeysAndInclude = selectedFields => {
|
export const extractKeysAndInclude = (selectedFields) => {
|
||||||
selectedFields = selectedFields.filter(
|
selectedFields = selectedFields.filter(
|
||||||
field => !field.includes('__typename')
|
(field) => !field.includes('__typename')
|
||||||
);
|
);
|
||||||
|
|
||||||
// Handles "id" field for both current and included objects
|
// Handles "id" field for both current and included objects
|
||||||
selectedFields = selectedFields.map(field => {
|
selectedFields = selectedFields.map((field) => {
|
||||||
if (field === 'id') return 'objectId';
|
if (field === 'id') return 'objectId';
|
||||||
return field.endsWith('.id')
|
return field.endsWith('.id')
|
||||||
? `${field.substring(0, field.lastIndexOf('.id'))}.objectId`
|
? `${field.substring(0, field.lastIndexOf('.id'))}.objectId`
|
||||||
@@ -36,25 +35,21 @@ export const extractKeysAndInclude = selectedFields => {
|
|||||||
});
|
});
|
||||||
let keys = undefined;
|
let keys = undefined;
|
||||||
let include = undefined;
|
let include = undefined;
|
||||||
|
|
||||||
if (selectedFields.length > 0) {
|
if (selectedFields.length > 0) {
|
||||||
keys = selectedFields.join(',');
|
keys = [...new Set(selectedFields)].join(',');
|
||||||
include = selectedFields
|
// We can use this shortcut since optimization is handled
|
||||||
.reduce((fields, field) => {
|
// later on RestQuery, avoid overhead here.
|
||||||
fields = fields.slice();
|
include = keys;
|
||||||
let pointIndex = field.lastIndexOf('.');
|
|
||||||
while (pointIndex > 0) {
|
|
||||||
const lastField = field.slice(pointIndex + 1);
|
|
||||||
field = field.slice(0, pointIndex);
|
|
||||||
if (!fields.includes(field) && lastField !== 'objectId') {
|
|
||||||
fields.push(field);
|
|
||||||
}
|
}
|
||||||
pointIndex = field.lastIndexOf('.');
|
|
||||||
}
|
return {
|
||||||
return fields;
|
// If authData is detected keys will not work properly
|
||||||
}, [])
|
// since authData has a special storage behavior
|
||||||
.join(',');
|
// so we need to skip keys currently
|
||||||
}
|
keys: keys && keys.indexOf('authData') === -1 ? keys : undefined,
|
||||||
return { keys, include };
|
include,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getParseClassMutationConfig = function (parseClassConfig) {
|
export const getParseClassMutationConfig = function (parseClassConfig) {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -118,4 +118,3 @@
|
|||||||
* @property {String[]} paths An array of paths for which the feature should be enabled. The mount path must not be included, for example instead of `/parse/functions/myFunction` specifiy `functions/myFunction`. The entries are interpreted as regular expression, for example `functions/.*` matches all functions, `jobs/.*` matches all jobs, `classes/.*` matches all classes, `.*` matches all paths.
|
* @property {String[]} paths An array of paths for which the feature should be enabled. The mount path must not be included, for example instead of `/parse/functions/myFunction` specifiy `functions/myFunction`. The entries are interpreted as regular expression, for example `functions/.*` matches all functions, `jobs/.*` matches all jobs, `classes/.*` matches all classes, `.*` matches all paths.
|
||||||
* @property {Number} ttl The duration in seconds after which a request record is discarded from the database, defaults to 300s.
|
* @property {Number} ttl The duration in seconds after which a request record is discarded from the database, defaults to 300s.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ class ParseServer {
|
|||||||
serverStartComplete();
|
serverStartComplete();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch(error => {
|
||||||
if (serverStartComplete) {
|
if (serverStartComplete) {
|
||||||
serverStartComplete(error);
|
serverStartComplete(error);
|
||||||
} else {
|
} else {
|
||||||
@@ -183,7 +183,7 @@ class ParseServer {
|
|||||||
if (!process.env.TESTING) {
|
if (!process.env.TESTING) {
|
||||||
//This causes tests to spew some useless warnings, so disable in test
|
//This causes tests to spew some useless warnings, so disable in test
|
||||||
/* istanbul ignore next */
|
/* istanbul ignore next */
|
||||||
process.on('uncaughtException', (err) => {
|
process.on('uncaughtException', err => {
|
||||||
if (err.code === 'EADDRINUSE') {
|
if (err.code === 'EADDRINUSE') {
|
||||||
// user-friendly message for this common error
|
// user-friendly message for this common error
|
||||||
process.stderr.write(
|
process.stderr.write(
|
||||||
@@ -270,7 +270,10 @@ class ParseServer {
|
|||||||
graphQLCustomTypeDefs = parse(
|
graphQLCustomTypeDefs = parse(
|
||||||
fs.readFileSync(options.graphQLSchema, 'utf8')
|
fs.readFileSync(options.graphQLSchema, 'utf8')
|
||||||
);
|
);
|
||||||
} else if (typeof options.graphQLSchema === 'object' || typeof options.graphQLSchema === 'function') {
|
} else if (
|
||||||
|
typeof options.graphQLSchema === 'object' ||
|
||||||
|
typeof options.graphQLSchema === 'function'
|
||||||
|
) {
|
||||||
graphQLCustomTypeDefs = options.graphQLSchema;
|
graphQLCustomTypeDefs = options.graphQLSchema;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -338,8 +341,8 @@ class ParseServer {
|
|||||||
if (Parse.serverURL) {
|
if (Parse.serverURL) {
|
||||||
const request = require('./request');
|
const request = require('./request');
|
||||||
request({ url: Parse.serverURL.replace(/\/$/, '') + '/health' })
|
request({ url: Parse.serverURL.replace(/\/$/, '') + '/health' })
|
||||||
.catch((response) => response)
|
.catch(response => response)
|
||||||
.then((response) => {
|
.then(response => {
|
||||||
const json = response.data || null;
|
const json = response.data || null;
|
||||||
if (
|
if (
|
||||||
response.status !== 200 ||
|
response.status !== 200 ||
|
||||||
@@ -372,7 +375,7 @@ function addParseCloud() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function injectDefaults(options: ParseServerOptions) {
|
function injectDefaults(options: ParseServerOptions) {
|
||||||
Object.keys(defaults).forEach((key) => {
|
Object.keys(defaults).forEach(key => {
|
||||||
if (!Object.prototype.hasOwnProperty.call(options, key)) {
|
if (!Object.prototype.hasOwnProperty.call(options, key)) {
|
||||||
options[key] = defaults[key];
|
options[key] = defaults[key];
|
||||||
}
|
}
|
||||||
@@ -428,12 +431,12 @@ function injectDefaults(options: ParseServerOptions) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Merge protectedFields options with defaults.
|
// Merge protectedFields options with defaults.
|
||||||
Object.keys(defaults.protectedFields).forEach((c) => {
|
Object.keys(defaults.protectedFields).forEach(c => {
|
||||||
const cur = options.protectedFields[c];
|
const cur = options.protectedFields[c];
|
||||||
if (!cur) {
|
if (!cur) {
|
||||||
options.protectedFields[c] = defaults.protectedFields[c];
|
options.protectedFields[c] = defaults.protectedFields[c];
|
||||||
} else {
|
} else {
|
||||||
Object.keys(defaults.protectedFields[c]).forEach((r) => {
|
Object.keys(defaults.protectedFields[c]).forEach(r => {
|
||||||
const unq = new Set([
|
const unq = new Set([
|
||||||
...(options.protectedFields[c][r] || []),
|
...(options.protectedFields[c][r] || []),
|
||||||
...defaults.protectedFields[c][r],
|
...defaults.protectedFields[c][r],
|
||||||
@@ -457,7 +460,7 @@ function configureListeners(parseServer) {
|
|||||||
const sockets = {};
|
const sockets = {};
|
||||||
/* Currently, express doesn't shut down immediately after receiving SIGINT/SIGTERM if it has client connections that haven't timed out. (This is a known issue with node - https://github.com/nodejs/node/issues/2642)
|
/* Currently, express doesn't shut down immediately after receiving SIGINT/SIGTERM if it has client connections that haven't timed out. (This is a known issue with node - https://github.com/nodejs/node/issues/2642)
|
||||||
This function, along with `destroyAliveConnections()`, intend to fix this behavior such that parse server will close all open connections and initiate the shutdown process as soon as it receives a SIGINT/SIGTERM signal. */
|
This function, along with `destroyAliveConnections()`, intend to fix this behavior such that parse server will close all open connections and initiate the shutdown process as soon as it receives a SIGINT/SIGTERM signal. */
|
||||||
server.on('connection', (socket) => {
|
server.on('connection', socket => {
|
||||||
const socketId = socket.remoteAddress + ':' + socket.remotePort;
|
const socketId = socket.remoteAddress + ':' + socket.remotePort;
|
||||||
sockets[socketId] = socket;
|
sockets[socketId] = socket;
|
||||||
socket.on('close', () => {
|
socket.on('close', () => {
|
||||||
|
|||||||
@@ -251,9 +251,14 @@ export class ClassesRouter extends PromiseRouter {
|
|||||||
this.route('POST', '/classes/:className', promiseEnsureIdempotency, req => {
|
this.route('POST', '/classes/:className', promiseEnsureIdempotency, req => {
|
||||||
return this.handleCreate(req);
|
return this.handleCreate(req);
|
||||||
});
|
});
|
||||||
this.route('PUT', '/classes/:className/:objectId', promiseEnsureIdempotency, req => {
|
this.route(
|
||||||
|
'PUT',
|
||||||
|
'/classes/:className/:objectId',
|
||||||
|
promiseEnsureIdempotency,
|
||||||
|
req => {
|
||||||
return this.handleUpdate(req);
|
return this.handleUpdate(req);
|
||||||
});
|
}
|
||||||
|
);
|
||||||
this.route('DELETE', '/classes/:className/:objectId', req => {
|
this.route('DELETE', '/classes/:className/:objectId', req => {
|
||||||
return this.handleDelete(req);
|
return this.handleDelete(req);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,7 +4,10 @@ var Parse = require('parse/node').Parse,
|
|||||||
triggers = require('../triggers');
|
triggers = require('../triggers');
|
||||||
|
|
||||||
import PromiseRouter from '../PromiseRouter';
|
import PromiseRouter from '../PromiseRouter';
|
||||||
import { promiseEnforceMasterKeyAccess, promiseEnsureIdempotency } from '../middlewares';
|
import {
|
||||||
|
promiseEnforceMasterKeyAccess,
|
||||||
|
promiseEnsureIdempotency,
|
||||||
|
} from '../middlewares';
|
||||||
import { jobStatusHandler } from '../StatusHandler';
|
import { jobStatusHandler } from '../StatusHandler';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { logger } from '../logger';
|
import { logger } from '../logger';
|
||||||
|
|||||||
@@ -40,9 +40,14 @@ export class InstallationsRouter extends ClassesRouter {
|
|||||||
this.route('POST', '/installations', promiseEnsureIdempotency, req => {
|
this.route('POST', '/installations', promiseEnsureIdempotency, req => {
|
||||||
return this.handleCreate(req);
|
return this.handleCreate(req);
|
||||||
});
|
});
|
||||||
this.route('PUT', '/installations/:objectId', promiseEnsureIdempotency, req => {
|
this.route(
|
||||||
|
'PUT',
|
||||||
|
'/installations/:objectId',
|
||||||
|
promiseEnsureIdempotency,
|
||||||
|
req => {
|
||||||
return this.handleUpdate(req);
|
return this.handleUpdate(req);
|
||||||
});
|
}
|
||||||
|
);
|
||||||
this.route('DELETE', '/installations/:objectId', req => {
|
this.route('DELETE', '/installations/:objectId', req => {
|
||||||
return this.handleDelete(req);
|
return this.handleDelete(req);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -239,13 +239,13 @@ export function handleParseHeaders(req, res, next) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then((auth) => {
|
.then(auth => {
|
||||||
if (auth) {
|
if (auth) {
|
||||||
req.auth = auth;
|
req.auth = auth;
|
||||||
next();
|
next();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch(error => {
|
||||||
if (error instanceof Parse.Error) {
|
if (error instanceof Parse.Error) {
|
||||||
next(error);
|
next(error);
|
||||||
return;
|
return;
|
||||||
@@ -416,12 +416,16 @@ export function promiseEnforceMasterKeyAccess(request) {
|
|||||||
*/
|
*/
|
||||||
export function promiseEnsureIdempotency(req) {
|
export function promiseEnsureIdempotency(req) {
|
||||||
// Enable feature only for MongoDB
|
// Enable feature only for MongoDB
|
||||||
if (!(req.config.database.adapter instanceof MongoStorageAdapter)) { return Promise.resolve(); }
|
if (!(req.config.database.adapter instanceof MongoStorageAdapter)) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
// Get parameters
|
// Get parameters
|
||||||
const config = req.config;
|
const config = req.config;
|
||||||
const requestId = ((req || {}).headers || {})["x-parse-request-id"];
|
const requestId = ((req || {}).headers || {})['x-parse-request-id'];
|
||||||
const { paths, ttl } = config.idempotencyOptions;
|
const { paths, ttl } = config.idempotencyOptions;
|
||||||
if (!requestId || !config.idempotencyOptions) { return Promise.resolve(); }
|
if (!requestId || !config.idempotencyOptions) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
// Request path may contain trailing slashes, depending on the original request, so remove
|
// Request path may contain trailing slashes, depending on the original request, so remove
|
||||||
// leading and trailing slashes to make it easier to specify paths in the configuration
|
// leading and trailing slashes to make it easier to specify paths in the configuration
|
||||||
const reqPath = req.path.replace(/^\/|\/$/, '');
|
const reqPath = req.path.replace(/^\/|\/$/, '');
|
||||||
@@ -435,15 +439,19 @@ export function promiseEnsureIdempotency(req) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!match) { return Promise.resolve(); }
|
if (!match) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
// Try to store request
|
// Try to store request
|
||||||
const expiryDate = new Date(new Date().setSeconds(new Date().getSeconds() + ttl));
|
const expiryDate = new Date(
|
||||||
return rest.create(
|
new Date().setSeconds(new Date().getSeconds() + ttl)
|
||||||
config,
|
);
|
||||||
auth.master(config),
|
return rest
|
||||||
'_Idempotency',
|
.create(config, auth.master(config), '_Idempotency', {
|
||||||
{ reqId: requestId, expire: Parse._encode(expiryDate) }
|
reqId: requestId,
|
||||||
).catch (e => {
|
expire: Parse._encode(expiryDate),
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
if (e.code == Parse.Error.DUPLICATE_VALUE) {
|
if (e.code == Parse.Error.DUPLICATE_VALUE) {
|
||||||
throw new Parse.Error(
|
throw new Parse.Error(
|
||||||
Parse.Error.DUPLICATE_REQUEST,
|
Parse.Error.DUPLICATE_REQUEST,
|
||||||
|
|||||||
30
src/rest.js
30
src/rest.js
@@ -31,7 +31,15 @@ function checkLiveQuery(className, config) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Returns a promise for an object with optional keys 'results' and 'count'.
|
// Returns a promise for an object with optional keys 'results' and 'count'.
|
||||||
function find(config, auth, className, restWhere, restOptions, clientSDK, context) {
|
function find(
|
||||||
|
config,
|
||||||
|
auth,
|
||||||
|
className,
|
||||||
|
restWhere,
|
||||||
|
restOptions,
|
||||||
|
clientSDK,
|
||||||
|
context
|
||||||
|
) {
|
||||||
enforceRoleSecurity('find', className, auth);
|
enforceRoleSecurity('find', className, auth);
|
||||||
return triggers
|
return triggers
|
||||||
.maybeRunQueryTrigger(
|
.maybeRunQueryTrigger(
|
||||||
@@ -59,7 +67,15 @@ function find(config, auth, className, restWhere, restOptions, clientSDK, contex
|
|||||||
}
|
}
|
||||||
|
|
||||||
// get is just like find but only queries an objectId.
|
// get is just like find but only queries an objectId.
|
||||||
const get = (config, auth, className, objectId, restOptions, clientSDK, context) => {
|
const get = (
|
||||||
|
config,
|
||||||
|
auth,
|
||||||
|
className,
|
||||||
|
objectId,
|
||||||
|
restOptions,
|
||||||
|
clientSDK,
|
||||||
|
context
|
||||||
|
) => {
|
||||||
var restWhere = { objectId };
|
var restWhere = { objectId };
|
||||||
enforceRoleSecurity('get', className, auth);
|
enforceRoleSecurity('get', className, auth);
|
||||||
return triggers
|
return triggers
|
||||||
@@ -218,7 +234,15 @@ function create(config, auth, className, restObject, clientSDK, context) {
|
|||||||
// Returns a promise that contains the fields of the update that the
|
// Returns a promise that contains the fields of the update that the
|
||||||
// REST API is supposed to return.
|
// REST API is supposed to return.
|
||||||
// Usually, this is just updatedAt.
|
// Usually, this is just updatedAt.
|
||||||
function update(config, auth, className, restWhere, restObject, clientSDK, context) {
|
function update(
|
||||||
|
config,
|
||||||
|
auth,
|
||||||
|
className,
|
||||||
|
restWhere,
|
||||||
|
restObject,
|
||||||
|
clientSDK,
|
||||||
|
context
|
||||||
|
) {
|
||||||
enforceRoleSecurity('update', className, auth);
|
enforceRoleSecurity('update', className, auth);
|
||||||
|
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
|
|||||||
Reference in New Issue
Block a user