diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 62890394..2463901a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,7 +6,7 @@ on: branches: - '**' env: - NODE_VERSION: 14.18.1 + NODE_VERSION: 16.13.0 PARSE_SERVER_TEST_TIMEOUT: 20000 jobs: check-ci: @@ -105,43 +105,43 @@ jobs: MONGODB_VERSION: 5.0.3 MONGODB_TOPOLOGY: replicaset MONGODB_STORAGE_ENGINE: wiredTiger - NODE_VERSION: 14.18.1 + NODE_VERSION: 16.13.0 - name: MongoDB 4.4, ReplicaSet, WiredTiger MONGODB_VERSION: 4.4.10 MONGODB_TOPOLOGY: replicaset MONGODB_STORAGE_ENGINE: wiredTiger - NODE_VERSION: 14.18.1 + NODE_VERSION: 16.13.0 - name: MongoDB 4.2, ReplicaSet, WiredTiger MONGODB_VERSION: 4.2.17 MONGODB_TOPOLOGY: replicaset MONGODB_STORAGE_ENGINE: wiredTiger - NODE_VERSION: 14.18.1 + NODE_VERSION: 16.13.0 - name: MongoDB 4.0, ReplicaSet, WiredTiger MONGODB_VERSION: 4.0.27 MONGODB_TOPOLOGY: replicaset MONGODB_STORAGE_ENGINE: wiredTiger - NODE_VERSION: 14.18.1 + NODE_VERSION: 16.13.0 - name: MongoDB 4.0, Standalone, MMAPv1 MONGODB_VERSION: 4.0.27 MONGODB_TOPOLOGY: standalone MONGODB_STORAGE_ENGINE: mmapv1 - NODE_VERSION: 14.18.1 + NODE_VERSION: 16.13.0 - name: Redis Cache PARSE_SERVER_TEST_CACHE: redis MONGODB_VERSION: 4.4.10 MONGODB_TOPOLOGY: standalone MONGODB_STORAGE_ENGINE: wiredTiger - NODE_VERSION: 14.18.1 + NODE_VERSION: 16.13.0 - name: Node 12 MONGODB_VERSION: 4.4.10 MONGODB_TOPOLOGY: standalone MONGODB_STORAGE_ENGINE: wiredTiger NODE_VERSION: 12.22.7 - - name: Node 15 + - name: Node 14 MONGODB_VERSION: 4.4.10 MONGODB_TOPOLOGY: standalone MONGODB_STORAGE_ENGINE: wiredTiger - NODE_VERSION: 15.14.0 + NODE_VERSION: 14.18.1 fail-fast: false name: ${{ matrix.name }} timeout-minutes: 15 @@ -183,19 +183,22 @@ jobs: include: - name: PostgreSQL 11, PostGIS 3.0 POSTGRES_IMAGE: postgis/postgis:11-3.0 - NODE_VERSION: 14.18.1 + NODE_VERSION: 16.13.0 - name: PostgreSQL 11, PostGIS 3.1 POSTGRES_IMAGE: postgis/postgis:11-3.1 - NODE_VERSION: 14.18.1 - - name: PostgreSQL 12, PostGIS 3.1 - POSTGRES_IMAGE: postgis/postgis:12-3.1 - NODE_VERSION: 14.18.1 - - name: PostgreSQL 13, PostGIS 3.1 - POSTGRES_IMAGE: postgis/postgis:13-3.1 - NODE_VERSION: 14.18.1 - - name: PostgreSQL 14, PostGIS 3.1 - POSTGRES_IMAGE: postgis/postgis:14-3.1 - NODE_VERSION: 14.18.1 + NODE_VERSION: 16.13.0 + - name: PostgreSQL 11, PostGIS 3.2 + POSTGRES_IMAGE: postgis/postgis:11-3.2 + NODE_VERSION: 16.13.0 + - name: PostgreSQL 12, PostGIS 3.2 + POSTGRES_IMAGE: postgis/postgis:12-3.2 + NODE_VERSION: 16.13.0 + - name: PostgreSQL 13, PostGIS 3.2 + POSTGRES_IMAGE: postgis/postgis:13-3.2 + NODE_VERSION: 16.13.0 + - name: PostgreSQL 14, PostGIS 3.2 + POSTGRES_IMAGE: postgis/postgis:14-3.2 + NODE_VERSION: 16.13.0 fail-fast: false name: ${{ matrix.name }} timeout-minutes: 15 diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c12e7cf..51ff85f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,8 +42,7 @@ Details: - Suitable environment: experimental -[log_release]: https://github.com/parse-community/parse-server/blob/release/CHANGELOG.md - +[log_release]: https://github.com/parse-community/parse-server/blob/release/changelogs/CHANGELOG_release.md [log_beta]: https://github.com/parse-community/parse-server/blob/beta/changelogs/CHANGELOG_beta.md [log_alpha]: https://github.com/parse-community/parse-server/blob/alpha/changelogs/CHANGELOG_alpha.md [branch_release]: https://github.com/parse-community/parse-server/tree/release diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7e620fbc..df5df279 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -22,6 +22,9 @@ - [Pull Request](#pull-request) - [Breaking Change](#breaking-change) - [Merging](#merging) + - [Breaking Change](#breaking-change-1) + - [Reverting](#reverting) + - [Major Release / Long-Term-Support](#major-release--long-term-support) - [Versioning](#versioning) - [Code of Conduct](#code-of-conduct) @@ -154,7 +157,7 @@ If your pull request introduces a change that may affect the storage or retrieva [PostGIS images (select one with v2.2 or higher) on docker dashboard](https://hub.docker.com/r/postgis/postgis) is based off of the official [postgres](https://registry.hub.docker.com/_/postgres/) image and will work out-of-the-box (as long as you create a user with the necessary extensions for each of your Parse databases; see below). To launch the compatible Postgres instance, copy and paste the following line into your shell: ``` -docker run -d --name parse-postgres -p 5432:5432 -e POSTGRES_PASSWORD=password --rm postgis/postgis:13-3.1-alpine && sleep 20 && docker exec -it parse-postgres psql -U postgres -c 'CREATE DATABASE parse_server_postgres_adapter_test_database;' && docker exec -it parse-postgres psql -U postgres -c 'CREATE EXTENSION pgcrypto; CREATE EXTENSION postgis;' -d parse_server_postgres_adapter_test_database && docker exec -it parse-postgres psql -U postgres -c 'CREATE EXTENSION postgis_topology;' -d parse_server_postgres_adapter_test_database +docker run -d --name parse-postgres -p 5432:5432 -e POSTGRES_PASSWORD=password --rm postgis/postgis:13-3.2-alpine && sleep 20 && docker exec -it parse-postgres psql -U postgres -c 'CREATE DATABASE parse_server_postgres_adapter_test_database;' && docker exec -it parse-postgres psql -U postgres -c 'CREATE EXTENSION pgcrypto; CREATE EXTENSION postgis;' -d parse_server_postgres_adapter_test_database && docker exec -it parse-postgres psql -U postgres -c 'CREATE EXTENSION postgis_topology;' -d parse_server_postgres_adapter_test_database ``` To stop the Postgres instance: @@ -162,7 +165,7 @@ To stop the Postgres instance: docker stop parse-postgres ``` -You can also use the [postgis/postgis:13-3.1-alpine](https://hub.docker.com/r/postgis/postgis) image in a Dockerfile and copy this [script](https://github.com/parse-community/parse-server/blob/master/scripts/before_script_postgres.sh) to the image by adding the following lines: +You can also use the [postgis/postgis:13-3.2-alpine](https://hub.docker.com/r/postgis/postgis) image in a Dockerfile and copy this [script](https://github.com/parse-community/parse-server/blob/master/scripts/before_script_postgres.sh) to the image by adding the following lines: ``` #Install additional scripts. These are run in abc order during initial start @@ -303,7 +306,7 @@ For release automation, the title of pull requests needs to be written in a defi ``` The _type_ is the category of change that is made, possible types are: -- `feat` - add a new feature +- `feat` - add a new feature or improve an existing feature - `fix` - fix a bug - `refactor` - refactor code without impact on features or performance - `docs` - add or edit code comments, documentation, GitHub pages @@ -335,8 +338,13 @@ If a pull request contains a braking change, the description of the pull request The following guide is for anyone who merges a contributor pull request into the working branch, the working branch into a release branch, a release branch into another release branch, or any other direct commits such as hotfixes into release branches or the working branch. -- For changelog generation, only the commit message set when merging the pull request is relevant. The title and description of the GitHub pull request as authored by the contributor have no influence on the changelog generation. However, the title of the GitHub pull request should be used as the commit message. -- If the pull request contains a breaking change, the commit message must contain the phrase `BREAKING CHANGE`, capitalized and without any formatting, followed by a short description of the breaking change and ideally how the developer should address it, all in a single line. This line should contain more details focusing on the "breaking” aspect of the change and is intended to assist the developer in adapting. Keep it concise, as it will become part of the changelog entry, for example: +- A contributor pull request must be merged into the working branch using `Squash and Merge`, to create a single commit message that describes the change. +- A release branch or the default branch must be merged into another release branch using `Merge Commit`, to preserve each individual commit message that describes its respective change. +- For changelog generation, only the commit message set when merging the pull request is relevant. The title and description of the GitHub pull request as authored by the contributor have no influence on the changelog generation. However, the title of the GitHub pull request should be used as the commit message. See the following chapters for considerations in special scenarios, e.g. merging a breaking change or reverting a commit. + +### Breaking Change + +If the pull request contains a breaking change, the commit message must contain the phrase `BREAKING CHANGE`, capitalized and without any formatting, followed by a short description of the breaking change and ideally how the developer should address it, all in a single line. This line should contain more details focusing on the "breaking” aspect of the change and is intended to assist the developer in adapting. Keep it concise, as it will become part of the changelog entry, for example: ``` fix: remove handle from door @@ -344,8 +352,33 @@ The following guide is for anyone who merges a contributor pull request into the BREAKING CHANGE: You cannot open the door anymore by using a handle. See the [#migration guide](http://example.com) for more details. ``` Keep in mind that in a repository with release automation, merging such a commit message will trigger a release with a major version increment. -- A contributor pull request must be merged into the working branch using `Squash and Merge`, to create a single commit message that describes the change. -- A release branch or the default branch must be merged into another release branch using `Merge Commit`, to preserve each individual commit message that describes its respective change. + +### Reverting + +If the commit reverts a previous commit, use the prefix `revert:`, followed by the header of the reverted commit. In the body of the commit message add `This reverts commit .`, where the hash is the SHA of the commit being reverted. For example: + + ``` + revert: fix: remove handle from door + + This reverts commit 1234567890abcdef. + ``` + +### Major Release / Long-Term-Support + +Long-Term-Support (LTS) is provided for the previous Parse Server major version. For example, Parse Server 4.x will receive security updates until Parse Server 5.x is superseded by Parse Server 6.x and becomes the new LTS version. While the current major version is published on branch `release`, a LTS version is published on branch `release-#.x.x`, for example `release-4.x.x` for the Parse Server 4.x LTS branch. + +#### Preparing Release + +The following changes are done in the `alpha` branch, before publishing the last `beta` version that will eventually become the major release. This way the changes trickle naturally through all branches and code consistency is ensured among branches. + +- Make sure all [deprecations](https://github.com/parse-community/parse-server/blob/alpha/DEPRECATIONS.md) are reflected in code, old code is removed and the deprecations table is updated. +- Add the future LTS branch `release-#.x.x` to the branch list in [release.config.js](https://github.com/parse-community/parse-server/blob/alpha/release.config.js) so that the branch will later be recognized for release automation. + +#### Publishing Release + +1. Create LTS branch `release-#.x.x` off the latest version tag on `release` branch. +2. Create temporary branch `build-release` off branch `beta` and create a pull request with `release` as the base branch. +3. Merge branch `build-release` into `release`. Given that there will be breaking changes, a new major release will be created. In the unlikely case that there have been no breaking changes between the previous major release and the upcoming release, a major version increment has to be triggered manually. See the docs of the release automation framework for how to do that. ## Versioning diff --git a/README.md b/README.md index 98840adb..7b507e9c 100644 --- a/README.md +++ b/README.md @@ -5,17 +5,16 @@ [![Build Status](https://github.com/parse-community/parse-server/workflows/ci/badge.svg?branch=alpha)](https://github.com/parse-community/parse-server/actions?query=workflow%3Aci+branch%3Aalpha) [![Snyk Badge](https://snyk.io/test/github/parse-community/parse-server/badge.svg)](https://snyk.io/test/github/parse-community/parse-server) [![Coverage](https://img.shields.io/codecov/c/github/parse-community/parse-server/alpha.svg)](https://codecov.io/github/parse-community/parse-server?branch=alpha) +[![auto-release](https://img.shields.io/badge/%F0%9F%9A%80-auto--release-9e34eb.svg)](https://github.com/parse-community/parse-dashboard/releases) -[![Node Version](https://img.shields.io/badge/nodejs-12,_14,_15-green.svg?logo=node.js&style=flat)](https://nodejs.org) +[![Node Version](https://img.shields.io/badge/nodejs-12,_14,_16-green.svg?logo=node.js&style=flat)](https://nodejs.org) [![MongoDB Version](https://img.shields.io/badge/mongodb-4.0,_4.2,_4.4,_5.0-green.svg?logo=mongodb&style=flat)](https://www.mongodb.com) [![Postgres Version](https://img.shields.io/badge/postgresql-11,_12,_13,_14-green.svg?logo=postgresql&style=flat)](https://www.postgresql.org) -[![auto-release](https://img.shields.io/badge/%F0%9F%9A%80-auto--release-9e34eb.svg)](https://github.com/parse-community/parse-dashboard/releases) [![npm latest version](https://img.shields.io/npm/v/parse-server/latest.svg)](https://www.npmjs.com/package/parse-server) [![npm beta version](https://img.shields.io/npm/v/parse-server/beta.svg)](https://www.npmjs.com/package/parse-server) [![npm alpha version](https://img.shields.io/npm/v/parse-server/alpha.svg)](https://www.npmjs.com/package/parse-server) - [![Backers on Open Collective](https://opencollective.com/parse-server/backers/badge.svg)][open-collective-link] [![Sponsors on Open Collective](https://opencollective.com/parse-server/sponsors/badge.svg)][open-collective-link] [![License][license-svg]][license-link] @@ -30,11 +29,15 @@ The full documentation for Parse Server is available in the [wiki](https://githu --- -A big *thank you* to all our backers and sponsors who support the development of Parse Platform! +A big *thank you* πŸ™ to our [sponsors](#sponsors) and [backers](#backers) who support the development of Parse Platform! -### πŸ’Ž Diamond Sponsors - -[![Sponsor](https://opencollective.com/parse-server/sponsor/0/avatar.svg)](https://opencollective.com/parse-server/sponsor/0/website) +### Diamond Sponsors + +[![Diamond Sponsors](https://opencollective.com/parse-server/tiers/diamond-sponsor.svg?avatarHeight=70&button=false)](https://opencollective.com/parse-server/contribute/diamond-sponsor-10560) + +#### Bronze Sponsors + +[![Bronze Sponsors](https://opencollective.com/parse-server/tiers/bronze-sponsor.svg?avatarHeight=36&button=false)](https://opencollective.com/parse-server/contribute/bronze-sponsor-10559) --- @@ -113,8 +116,8 @@ Parse Server is continuously tested with the most recent releases of Node.js to |------------|----------------|-------------|---------------| | Node.js 12 | 12.22.7 | April 2022 | βœ… Yes | | Node.js 14 | 14.18.1 | April 2023 | βœ… Yes | -| Node.js 15 | 15.14.0 | June 2021 | βœ… Yes | -| Node.js 16 | 16.x.x | April 2024 | ❌ Not tested | +| Node.js 16 | 16.13.0 | April 2024 | βœ… Yes | +| Node.js 17 | 17.x | June 2022 | ❌ Not tested | #### MongoDB Parse Server is continuously tested with the most recent releases of MongoDB to ensure compatibility. We follow the [MongoDB support schedule](https://www.mongodb.com/support-policy) and only test against versions that are officially supported and have not reached their end-of-life date. @@ -131,10 +134,10 @@ Parse Server is continuously tested with the most recent releases of PostgreSQL | Version | PostGIS Version | End-of-Life | Parse Server Support End | Compatible | |-------------|-----------------|---------------|--------------------------|------------| -| Postgres 11 | 3.0, 3.1 | November 2023 | April 2022 | βœ… Yes | -| Postgres 12 | 3.1 | November 2024 | April 2023 | βœ… Yes | -| Postgres 13 | 3.1 | November 2025 | April 2024 | βœ… Yes | -| Postgres 14 | 3.1 | November 2026 | April 2025 | βœ… Yes | +| Postgres 11 | 3.0, 3.1, 3.2 | November 2023 | April 2022 | βœ… Yes | +| Postgres 12 | 3.2 | November 2024 | April 2023 | βœ… Yes | +| Postgres 13 | 3.2 | November 2025 | April 2024 | βœ… Yes | +| Postgres 14 | 3.2 | November 2026 | April 2025 | βœ… Yes | ### Locally ```bash @@ -486,11 +489,12 @@ You can also find more adapters maintained by the community by searching on [npm Parse Server allows developers to choose from several options when hosting files: -* `GridFSBucketAdapter`, which is backed by MongoDB; -* `S3Adapter`, which is backed by [Amazon S3](https://aws.amazon.com/s3/); or -* `GCSAdapter`, which is backed by [Google Cloud Storage](https://cloud.google.com/storage/) +* `GridFSBucketAdapter` - which is backed by MongoDB +* `S3Adapter` - which is backed by [Amazon S3](https://aws.amazon.com/s3/) +* `GCSAdapter` - which is backed by [Google Cloud Storage](https://cloud.google.com/storage/) +* `FSAdapter` - local file storage -`GridFSBucketAdapter` is used by default and requires no setup, but if you're interested in using S3 or Google Cloud Storage, additional configuration information is available in the [Parse Server guide](http://docs.parseplatform.org/parse-server/guide/#configuring-file-adapters). +`GridFSBucketAdapter` is used by default and requires no setup, but if you're interested in using Amazon S3, Google Cloud Storage, or local file storage, additional configuration information is available in the [Parse Server guide](http://docs.parseplatform.org/parse-server/guide/#configuring-file-adapters). ## Idempotency Enforcement @@ -521,9 +525,26 @@ let api = new ParseServer({ | `idempotencyOptions.paths` | yes | `Array` | `[]` | `.*` (all paths, includes the examples below),
`functions/.*` (all functions),
`jobs/.*` (all jobs),
`classes/.*` (all classes),
`functions/.*` (all functions),
`users` (user creation / update),
`installations` (installation creation / update) | PARSE_SERVER_EXPERIMENTAL_IDEMPOTENCY_PATHS | An array of path patterns that have to match the request path for request deduplication to be enabled. The mount path must not be included, for example to match the request path `/parse/functions/myFunction` specify the path pattern `functions/myFunction`. A trailing slash of the request path is ignored, for example the path pattern `functions/myFunction` matches both `/parse/functions/myFunction` and `/parse/functions/myFunction/`. | | `idempotencyOptions.ttl` | yes | `Integer` | `300` | `60` (60 seconds) | PARSE_SERVER_EXPERIMENTAL_IDEMPOTENCY_TTL | The duration in seconds after which a request record is discarded from the database. Duplicate requests due to network issues can be expected to arrive within milliseconds up to several seconds. This value must be greater than `0`. | -### Notes +### Postgres -- This feature is currently only available for MongoDB and not for Postgres. +To use this feature in Postgres, you will need to create a cron job using [pgAdmin](https://www.pgadmin.org/docs/pgadmin4/development/pgagent_jobs.html) or similar to call the Postgres function `idempotency_delete_expired_records()` that deletes expired idempotency records. You can find an example script below. Make sure the script has the same privileges to log into Postgres as Parse Server. + +```bash +#!/bin/bash + +set -e +psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL + SELECT idempotency_delete_expired_records(); +EOSQL + +exec "$@" +``` + +Assuming the script above is named, `parse_idempotency_delete_expired_records.sh`, a cron job that runs the script every 2 minutes may look like: + +```bash +2 * * * * /root/parse_idempotency_delete_expired_records.sh >/dev/null 2>&1 +``` ## Localization diff --git a/changelogs/CHANGELOG_alpha.md b/changelogs/CHANGELOG_alpha.md index 1d5c4e5e..0f800988 100644 --- a/changelogs/CHANGELOG_alpha.md +++ b/changelogs/CHANGELOG_alpha.md @@ -1,3 +1,186 @@ +# [5.0.0-alpha.29](https://github.com/parse-community/parse-server/compare/5.0.0-alpha.28...5.0.0-alpha.29) (2022-03-12) + + +### Features + +* bump required node engine to >=12.22.10 ([#7846](https://github.com/parse-community/parse-server/issues/7846)) ([5ace99d](https://github.com/parse-community/parse-server/commit/5ace99d542a11e422af46d9fd6b1d3d2513b34cf)) + + +### BREAKING CHANGES + +* This requires Node.js version >=12.22.10. ([5ace99d](5ace99d)) + +# [5.0.0-alpha.28](https://github.com/parse-community/parse-server/compare/5.0.0-alpha.27...5.0.0-alpha.28) (2022-03-12) + + +### Bug Fixes + +* security vulnerability that allows remote code execution (GHSA-p6h4-93qp-jhcm) ([#7844](https://github.com/parse-community/parse-server/issues/7844)) ([e569f40](https://github.com/parse-community/parse-server/commit/e569f402b1fd8648fb0d1523b71b2a03273902a5)) + +# [5.0.0-alpha.27](https://github.com/parse-community/parse-server/compare/5.0.0-alpha.26...5.0.0-alpha.27) (2022-03-12) + + +### Reverts + +* update node engine to 2.22.0 ([#7827](https://github.com/parse-community/parse-server/issues/7827)) ([f235412](https://github.com/parse-community/parse-server/commit/f235412c1b6c2b173b7531f285429ea7214b56a2)) + +# [5.0.0-alpha.26](https://github.com/parse-community/parse-server/compare/5.0.0-alpha.25...5.0.0-alpha.26) (2022-02-25) + + +### Bug Fixes + +* package.json & package-lock.json to reduce vulnerabilities ([#7823](https://github.com/parse-community/parse-server/issues/7823)) ([5ca2288](https://github.com/parse-community/parse-server/commit/5ca228882332b65f3ac05407e6e4da1ee3ef3749)) + +# [5.0.0-alpha.25](https://github.com/parse-community/parse-server/compare/5.0.0-alpha.24...5.0.0-alpha.25) (2022-02-23) + + +### Bug Fixes + +* upgrade winston from 3.5.0 to 3.5.1 ([#7820](https://github.com/parse-community/parse-server/issues/7820)) ([4af253d](https://github.com/parse-community/parse-server/commit/4af253d1f8654a6f57b5137ad310cdacadc922cc)) + +# [5.0.0-alpha.24](https://github.com/parse-community/parse-server/compare/5.0.0-alpha.23...5.0.0-alpha.24) (2022-02-10) + + +### Bug Fixes + +* security upgrade follow-redirects from 1.14.7 to 1.14.8 ([#7801](https://github.com/parse-community/parse-server/issues/7801)) ([70088a9](https://github.com/parse-community/parse-server/commit/70088a95a78393da2a4ac68be81e63107747626a)) + +# [5.0.0-alpha.23](https://github.com/parse-community/parse-server/compare/5.0.0-alpha.22...5.0.0-alpha.23) (2022-02-06) + + +### Bug Fixes + +* server crash using GraphQL due to missing @apollo/client peer dependency ([#7787](https://github.com/parse-community/parse-server/issues/7787)) ([08089d6](https://github.com/parse-community/parse-server/commit/08089d6fcbb215412448ce7d92b21b9fe6c929f2)) + +# [5.0.0-alpha.22](https://github.com/parse-community/parse-server/compare/5.0.0-alpha.21...5.0.0-alpha.22) (2022-02-06) + + +### Features + +* upgrade to MongoDB Node.js driver 4.x for MongoDB 5.0 support ([#7794](https://github.com/parse-community/parse-server/issues/7794)) ([f88aa2a](https://github.com/parse-community/parse-server/commit/f88aa2a62a533e5344d1c13dd38c5a0b283a480a)) + + +### BREAKING CHANGES + +* The MongoDB GridStore adapter has been removed. By default, Parse Server already uses GridFS, so if you do not manually use the GridStore adapter, you can ignore this change. ([f88aa2a](f88aa2a)) + +# [5.0.0-alpha.21](https://github.com/parse-community/parse-server/compare/5.0.0-alpha.20...5.0.0-alpha.21) (2022-01-25) + + +### Features + +* add Cloud Code context to `ParseObject.fetch` ([#7779](https://github.com/parse-community/parse-server/issues/7779)) ([315290d](https://github.com/parse-community/parse-server/commit/315290d16110110938f80a6b779cc2d1db58c552)) + +# [5.0.0-alpha.20](https://github.com/parse-community/parse-server/compare/5.0.0-alpha.19...5.0.0-alpha.20) (2022-01-22) + + +### Bug Fixes + +* bump node-fetch from 2.6.1 to 3.1.1 ([#7782](https://github.com/parse-community/parse-server/issues/7782)) ([9082351](https://github.com/parse-community/parse-server/commit/90823514113a1a085ebc818f7109b3fd7591346f)) + +# [5.0.0-alpha.19](https://github.com/parse-community/parse-server/compare/5.0.0-alpha.18...5.0.0-alpha.19) (2022-01-22) + + +### Bug Fixes + +* bump nanoid from 3.1.25 to 3.2.0 ([#7781](https://github.com/parse-community/parse-server/issues/7781)) ([f5f63bf](https://github.com/parse-community/parse-server/commit/f5f63bfc64d3481ed944ceb5e9f50b33dccd1ce9)) + +# [5.0.0-alpha.18](https://github.com/parse-community/parse-server/compare/5.0.0-alpha.17...5.0.0-alpha.18) (2022-01-13) + + +### Bug Fixes + +* security upgrade follow-redirects from 1.14.6 to 1.14.7 ([#7769](https://github.com/parse-community/parse-server/issues/7769)) ([8f5a861](https://github.com/parse-community/parse-server/commit/8f5a8618cfa7ed9a2a239a095abffa8f3fd8d31a)) + +# [5.0.0-alpha.17](https://github.com/parse-community/parse-server/compare/5.0.0-alpha.16...5.0.0-alpha.17) (2022-01-13) + + +### Bug Fixes + +* schema cache not cleared in some cases ([#7678](https://github.com/parse-community/parse-server/issues/7678)) ([5af6e5d](https://github.com/parse-community/parse-server/commit/5af6e5dfaa129b1a350afcba4fb381b21c4cc35d)) + +# [5.0.0-alpha.16](https://github.com/parse-community/parse-server/compare/5.0.0-alpha.15...5.0.0-alpha.16) (2022-01-02) + + +### Features + +* add Idempotency to Postgres ([#7750](https://github.com/parse-community/parse-server/issues/7750)) ([0c3feaa](https://github.com/parse-community/parse-server/commit/0c3feaaa1751964c0db89f25674935c3354b1538)) + +# [5.0.0-alpha.15](https://github.com/parse-community/parse-server/compare/5.0.0-alpha.14...5.0.0-alpha.15) (2022-01-02) + + +### Features + +* support `postgresql` protocol in database URI ([#7757](https://github.com/parse-community/parse-server/issues/7757)) ([caf4a23](https://github.com/parse-community/parse-server/commit/caf4a2341f554b28e3918c53e7e897a3ca47bf8b)) + +# [5.0.0-alpha.14](https://github.com/parse-community/parse-server/compare/5.0.0-alpha.13...5.0.0-alpha.14) (2022-01-02) + + +### Features + +* support relativeTime query constraint on Postgres ([#7747](https://github.com/parse-community/parse-server/issues/7747)) ([16b1b2a](https://github.com/parse-community/parse-server/commit/16b1b2a19714535ca805f2dbb3b561d8f6a519a7)) + +# [5.0.0-alpha.13](https://github.com/parse-community/parse-server/compare/5.0.0-alpha.12...5.0.0-alpha.13) (2021-12-08) + + +### Bug Fixes + +* node engine compatibility did not include node 16 ([#7739](https://github.com/parse-community/parse-server/issues/7739)) ([ea7c014](https://github.com/parse-community/parse-server/commit/ea7c01400f992a1263543706fe49b6174758a2d6)) + +# [5.0.0-alpha.12](https://github.com/parse-community/parse-server/compare/5.0.0-alpha.11...5.0.0-alpha.12) (2021-12-06) + + +### Bug Fixes + +* adding or modifying a nested property requires addField permissions ([#7679](https://github.com/parse-community/parse-server/issues/7679)) ([6a6248b](https://github.com/parse-community/parse-server/commit/6a6248b6cb2e732d17131e18e659943b894ed2f1)) + +# [5.0.0-alpha.11](https://github.com/parse-community/parse-server/compare/5.0.0-alpha.10...5.0.0-alpha.11) (2021-11-29) + + +### Bug Fixes + +* upgrade mime from 2.5.2 to 3.0.0 ([#7725](https://github.com/parse-community/parse-server/issues/7725)) ([f5ef98b](https://github.com/parse-community/parse-server/commit/f5ef98bde32083403c0e30a12162fcc1e52cac37)) + +# [5.0.0-alpha.10](https://github.com/parse-community/parse-server/compare/5.0.0-alpha.9...5.0.0-alpha.10) (2021-11-29) + + +### Bug Fixes + +* upgrade parse from 3.3.1 to 3.4.0 ([#7723](https://github.com/parse-community/parse-server/issues/7723)) ([d4c1f47](https://github.com/parse-community/parse-server/commit/d4c1f473073764cb0570c633fc4a30669c2ce889)) + +# [5.0.0-alpha.9](https://github.com/parse-community/parse-server/compare/5.0.0-alpha.8...5.0.0-alpha.9) (2021-11-27) + + +### Bug Fixes + +* unable to use objectId size higher than 19 on GraphQL API ([#7627](https://github.com/parse-community/parse-server/issues/7627)) ([ed86c80](https://github.com/parse-community/parse-server/commit/ed86c807721cc52a1a5a9dea0b768717eec269ed)) + +# [5.0.0-alpha.8](https://github.com/parse-community/parse-server/compare/5.0.0-alpha.7...5.0.0-alpha.8) (2021-11-18) + + +### Features + +* add support for Node 16 ([#7707](https://github.com/parse-community/parse-server/issues/7707)) ([45cc58c](https://github.com/parse-community/parse-server/commit/45cc58c7e5e640a46c5d508019a3aa81242964b1)) + + +### BREAKING CHANGES + +* Removes official Node 15 support which has reached it end-of-life date. ([45cc58c](45cc58c)) + +# [5.0.0-alpha.7](https://github.com/parse-community/parse-server/compare/5.0.0-alpha.6...5.0.0-alpha.7) (2021-11-12) + + +### Bug Fixes + +* node engine range has no upper limit to exclude incompatible node versions ([#7692](https://github.com/parse-community/parse-server/issues/7692)) ([573558d](https://github.com/parse-community/parse-server/commit/573558d3adcbcc6222c92003829867e1a73eef94)) + +# [5.0.0-alpha.6](https://github.com/parse-community/parse-server/compare/5.0.0-alpha.5...5.0.0-alpha.6) (2021-11-10) + + +### Reverts + +* refactor: allow ES import for cloud string if package type is module ([b64640c](https://github.com/parse-community/parse-server/commit/b64640c5705f733798783e68d216e957044ef23c)) + # [5.0.0-alpha.5](https://github.com/parse-community/parse-server/compare/5.0.0-alpha.4...5.0.0-alpha.5) (2021-11-01) diff --git a/changelogs/CHANGELOG_beta.md b/changelogs/CHANGELOG_beta.md index c49b9a22..4c6b49cc 100644 --- a/changelogs/CHANGELOG_beta.md +++ b/changelogs/CHANGELOG_beta.md @@ -1,3 +1,46 @@ +# [5.0.0-beta.10](https://github.com/parse-community/parse-server/compare/5.0.0-beta.9...5.0.0-beta.10) (2022-03-15) + + +### Bug Fixes + +* adding or modifying a nested property requires addField permissions ([#7679](https://github.com/parse-community/parse-server/issues/7679)) ([6a6248b](https://github.com/parse-community/parse-server/commit/6a6248b6cb2e732d17131e18e659943b894ed2f1)) +* bump nanoid from 3.1.25 to 3.2.0 ([#7781](https://github.com/parse-community/parse-server/issues/7781)) ([f5f63bf](https://github.com/parse-community/parse-server/commit/f5f63bfc64d3481ed944ceb5e9f50b33dccd1ce9)) +* bump node-fetch from 2.6.1 to 3.1.1 ([#7782](https://github.com/parse-community/parse-server/issues/7782)) ([9082351](https://github.com/parse-community/parse-server/commit/90823514113a1a085ebc818f7109b3fd7591346f)) +* node engine compatibility did not include node 16 ([#7739](https://github.com/parse-community/parse-server/issues/7739)) ([ea7c014](https://github.com/parse-community/parse-server/commit/ea7c01400f992a1263543706fe49b6174758a2d6)) +* node engine range has no upper limit to exclude incompatible node versions ([#7692](https://github.com/parse-community/parse-server/issues/7692)) ([573558d](https://github.com/parse-community/parse-server/commit/573558d3adcbcc6222c92003829867e1a73eef94)) +* package.json & package-lock.json to reduce vulnerabilities ([#7823](https://github.com/parse-community/parse-server/issues/7823)) ([5ca2288](https://github.com/parse-community/parse-server/commit/5ca228882332b65f3ac05407e6e4da1ee3ef3749)) +* schema cache not cleared in some cases ([#7678](https://github.com/parse-community/parse-server/issues/7678)) ([5af6e5d](https://github.com/parse-community/parse-server/commit/5af6e5dfaa129b1a350afcba4fb381b21c4cc35d)) +* security upgrade follow-redirects from 1.14.6 to 1.14.7 ([#7769](https://github.com/parse-community/parse-server/issues/7769)) ([8f5a861](https://github.com/parse-community/parse-server/commit/8f5a8618cfa7ed9a2a239a095abffa8f3fd8d31a)) +* security upgrade follow-redirects from 1.14.7 to 1.14.8 ([#7801](https://github.com/parse-community/parse-server/issues/7801)) ([70088a9](https://github.com/parse-community/parse-server/commit/70088a95a78393da2a4ac68be81e63107747626a)) +* security vulnerability that allows remote code execution (GHSA-p6h4-93qp-jhcm) ([#7844](https://github.com/parse-community/parse-server/issues/7844)) ([e569f40](https://github.com/parse-community/parse-server/commit/e569f402b1fd8648fb0d1523b71b2a03273902a5)) +* server crash using GraphQL due to missing @apollo/client peer dependency ([#7787](https://github.com/parse-community/parse-server/issues/7787)) ([08089d6](https://github.com/parse-community/parse-server/commit/08089d6fcbb215412448ce7d92b21b9fe6c929f2)) +* unable to use objectId size higher than 19 on GraphQL API ([#7627](https://github.com/parse-community/parse-server/issues/7627)) ([ed86c80](https://github.com/parse-community/parse-server/commit/ed86c807721cc52a1a5a9dea0b768717eec269ed)) +* upgrade mime from 2.5.2 to 3.0.0 ([#7725](https://github.com/parse-community/parse-server/issues/7725)) ([f5ef98b](https://github.com/parse-community/parse-server/commit/f5ef98bde32083403c0e30a12162fcc1e52cac37)) +* upgrade parse from 3.3.1 to 3.4.0 ([#7723](https://github.com/parse-community/parse-server/issues/7723)) ([d4c1f47](https://github.com/parse-community/parse-server/commit/d4c1f473073764cb0570c633fc4a30669c2ce889)) +* upgrade winston from 3.5.0 to 3.5.1 ([#7820](https://github.com/parse-community/parse-server/issues/7820)) ([4af253d](https://github.com/parse-community/parse-server/commit/4af253d1f8654a6f57b5137ad310cdacadc922cc)) + +### Features + +* add Cloud Code context to `ParseObject.fetch` ([#7779](https://github.com/parse-community/parse-server/issues/7779)) ([315290d](https://github.com/parse-community/parse-server/commit/315290d16110110938f80a6b779cc2d1db58c552)) +* add Idempotency to Postgres ([#7750](https://github.com/parse-community/parse-server/issues/7750)) ([0c3feaa](https://github.com/parse-community/parse-server/commit/0c3feaaa1751964c0db89f25674935c3354b1538)) +* add support for Node 16 ([#7707](https://github.com/parse-community/parse-server/issues/7707)) ([45cc58c](https://github.com/parse-community/parse-server/commit/45cc58c7e5e640a46c5d508019a3aa81242964b1)) +* bump required node engine to >=12.22.10 ([#7846](https://github.com/parse-community/parse-server/issues/7846)) ([5ace99d](https://github.com/parse-community/parse-server/commit/5ace99d542a11e422af46d9fd6b1d3d2513b34cf)) +* support `postgresql` protocol in database URI ([#7757](https://github.com/parse-community/parse-server/issues/7757)) ([caf4a23](https://github.com/parse-community/parse-server/commit/caf4a2341f554b28e3918c53e7e897a3ca47bf8b)) +* support relativeTime query constraint on Postgres ([#7747](https://github.com/parse-community/parse-server/issues/7747)) ([16b1b2a](https://github.com/parse-community/parse-server/commit/16b1b2a19714535ca805f2dbb3b561d8f6a519a7)) +* upgrade to MongoDB Node.js driver 4.x for MongoDB 5.0 support ([#7794](https://github.com/parse-community/parse-server/issues/7794)) ([f88aa2a](https://github.com/parse-community/parse-server/commit/f88aa2a62a533e5344d1c13dd38c5a0b283a480a)) + +### Reverts + +* refactor: allow ES import for cloud string if package type is module ([b64640c](https://github.com/parse-community/parse-server/commit/b64640c5705f733798783e68d216e957044ef23c)) +* update node engine to 2.22.0 ([#7827](https://github.com/parse-community/parse-server/issues/7827)) ([f235412](https://github.com/parse-community/parse-server/commit/f235412c1b6c2b173b7531f285429ea7214b56a2)) + + +### BREAKING CHANGES + +* This requires Node.js version >=12.22.10. ([5ace99d](5ace99d)) +* The MongoDB GridStore adapter has been removed. By default, Parse Server already uses GridFS, so if you do not manually use the GridStore adapter, you can ignore this change. ([f88aa2a](f88aa2a)) +* Removes official Node 15 support which has reached it end-of-life date. ([45cc58c](45cc58c)) + # [5.0.0-beta.9](https://github.com/parse-community/parse-server/compare/5.0.0-beta.8...5.0.0-beta.9) (2022-03-12) diff --git a/ci/ciCheck.js b/ci/ciCheck.js index e1d968be..2ad5c3e8 100644 --- a/ci/ciCheck.js +++ b/ci/ciCheck.js @@ -1,4 +1,4 @@ -'use strict' +'use strict'; const CiVersionCheck = require('./CiVersionCheck'); const mongoVersionList = require('mongodb-version-list'); @@ -14,9 +14,8 @@ async function check() { * Check the MongoDB versions used in test environments. */ async function checkMongoDbVersions() { - const releasedVersions = await new Promise((resolve, reject) => { - mongoVersionList(function(error, versions) { + mongoVersionList(function (error, versions) { if (error) { reject(error); } @@ -47,7 +46,6 @@ async function checkMongoDbVersions() { * Check the Nodejs versions used in test environments. */ async function checkNodeVersions() { - const allVersions = await allNodeVersions(); const releasedVersions = allVersions.versions; @@ -62,7 +60,8 @@ async function checkNodeVersions() { ignoreReleasedVersions: [ '<12.0.0', // These versions have reached their end-of-life support date '>=13.0.0 <14.0.0', // These versions have reached their end-of-life support date - '>=16.0.0', // This version has not been officially released yet + '>=15.0.0 <16.0.0', // These versions have reached their end-of-life support date + '>=17.0.0', // These versions are not officially supported yet ], }).check(); } diff --git a/package-lock.json b/package-lock.json index ea526b1c..0be4c2e2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "parse-server", - "version": "5.0.0", + "version": "5.0.0-beta.10", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -11,9 +11,9 @@ "dev": true }, "@apollo/client": { - "version": "3.4.8", - "resolved": "https://registry.npmjs.org/@apollo/client/-/client-3.4.8.tgz", - "integrity": "sha512-/cNqTSwc2Dw8q6FDDjdd30+yvhP7rI0Fvl3Hbro0lTtFuhzkevfNyQaI2jAiOrjU6Jc0RbanxULaNrX7UmvjSQ==", + "version": "3.5.8", + "resolved": "https://registry.npmjs.org/@apollo/client/-/client-3.5.8.tgz", + "integrity": "sha512-MAm05+I1ullr64VLpZwon/ISnkMuNLf6vDqgo9wiMhHYBGT4yOAbAIseRdjCHZwfSx/7AUuBgaTNOssZPIr6FQ==", "requires": { "@graphql-typed-document-node/core": "^3.0.0", "@wry/context": "^0.6.0", @@ -24,9 +24,9 @@ "optimism": "^0.16.1", "prop-types": "^15.7.2", "symbol-observable": "^4.0.0", - "ts-invariant": "^0.9.0", + "ts-invariant": "^0.9.4", "tslib": "^2.3.0", - "zen-observable-ts": "^1.1.0" + "zen-observable-ts": "^1.2.0" }, "dependencies": { "@wry/equality": { @@ -38,9 +38,9 @@ } }, "ts-invariant": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/ts-invariant/-/ts-invariant-0.9.1.tgz", - "integrity": "sha512-hSeYibh29ULlHkuEfukcoiyTct+s2RzczMLTv4x3NWC/YrBy7x7ps5eYq/b4Y3Sb9/uAlf54+/5CAEMVxPhuQw==", + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/ts-invariant/-/ts-invariant-0.9.4.tgz", + "integrity": "sha512-63jtX/ZSwnUNi/WhXjnK8kz4cHHpYS60AnmA6ixz17l7E12a5puCWFlNpkne5Rl0J8TBPVHpGjsj4fxs8ObVLQ==", "requires": { "tslib": "^2.1.0" } @@ -51,11 +51,10 @@ "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" }, "zen-observable-ts": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/zen-observable-ts/-/zen-observable-ts-1.1.0.tgz", - "integrity": "sha512-1h4zlLSqI2cRLPJUHJFL8bCWHhkpuXkF+dbGkRaWjgDIG26DmzyshUMrdV/rL3UnR+mhaX4fRq8LPouq0MYYIA==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/zen-observable-ts/-/zen-observable-ts-1.2.3.tgz", + "integrity": "sha512-hc/TGiPkAWpByykMwDcem3SdUgA4We+0Qb36bItSuJC9xD0XVBZoFHYoadAomDSNf64CG8Ydj0Qb8Od8BUWz5g==", "requires": { - "@types/zen-observable": "0.8.3", "zen-observable": "0.8.15" } } @@ -1104,6 +1103,7 @@ "version": "7.15.3", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.15.3.tgz", "integrity": "sha512-OvwMLqNXkCXSz1kSm58sEsNuhqOx/fKpnUnKnFB5v8uDda5bLNEHNgKPvhDN6IU0LDcnHQ90LlJ0Q6jnyBSIBA==", + "dev": true, "requires": { "regenerator-runtime": "^0.13.4" } @@ -1172,10 +1172,15 @@ "to-fast-properties": "^2.0.0" } }, + "@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==" + }, "@dabh/diagnostics": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.2.tgz", - "integrity": "sha512-+A1YivoVDNNVCdfozHSR8v/jyuuLTMXwjWuxPFlFlUapXoGc+Gj9mDlTDDfrwl7rXCl2tNZ0kE8sIBO6YOn96Q==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", + "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==", "requires": { "colorspace": "1.1.x", "enabled": "2.0.x", @@ -1192,6 +1197,37 @@ "tslib": "~2.0.1" } }, + "@graphql-tools/batch-execute": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/@graphql-tools/batch-execute/-/batch-execute-8.3.2.tgz", + "integrity": "sha512-ICWqM+MvEkIPHm18Q0cmkvm134zeQMomBKmTRxyxMNhL/ouz6Nqld52/brSlaHnzA3fczupeRJzZ0YatruGBcQ==", + "requires": { + "@graphql-tools/utils": "^8.6.2", + "dataloader": "2.0.0", + "tslib": "~2.3.0", + "value-or-promise": "1.0.11" + }, + "dependencies": { + "@graphql-tools/utils": { + "version": "8.6.2", + "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-8.6.2.tgz", + "integrity": "sha512-x1DG0cJgpJtImUlNE780B/dfp8pxvVxOD6UeykFH5rHes26S4kGokbgU8F1IgrJ1vAPm/OVBHtd2kicTsPfwdA==", + "requires": { + "tslib": "~2.3.0" + } + }, + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + }, + "value-or-promise": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/value-or-promise/-/value-or-promise-1.0.11.tgz", + "integrity": "sha512-41BrgH+dIbCFXClcSapVs5M6GkENd3gQOJpEfPDNa71LsUGMXDL0jMWpI/Rh7WhX+Aalfz2TTS3Zt5pUsbnhLg==" + } + } + }, "@graphql-tools/delegate": { "version": "6.2.4", "resolved": "https://registry.npmjs.org/@graphql-tools/delegate/-/delegate-6.2.4.tgz", @@ -1206,45 +1242,95 @@ } }, "@graphql-tools/links": { - "version": "6.2.5", - "resolved": "https://registry.npmjs.org/@graphql-tools/links/-/links-6.2.5.tgz", - "integrity": "sha512-XeGDioW7F+HK6HHD/zCeF0HRC9s12NfOXAKv1HC0J7D50F4qqMvhdS/OkjzLoBqsgh/Gm8icRc36B5s0rOA9ig==", + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/@graphql-tools/links/-/links-8.2.2.tgz", + "integrity": "sha512-lWyRvG4KqVj/3dpuQzZN34TXs9+5ETaT1MxhPHe6LIF/DdNQk4Q4Y7VeET/fZ8ZhbzgweMy0AA+ZkrS2HxBcgw==", "requires": { - "@graphql-tools/utils": "^7.0.0", - "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" + "@graphql-tools/delegate": "^8.5.1", + "@graphql-tools/utils": "^8.6.2", + "apollo-upload-client": "17.0.0", + "form-data": "^4.0.0", + "node-fetch": "^2.6.5", + "tslib": "~2.3.0" }, "dependencies": { - "@graphql-tools/utils": { - "version": "7.10.0", - "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-7.10.0.tgz", - "integrity": "sha512-d334r6bo9mxdSqZW6zWboEnnOOFRrAPVQJ7LkU8/6grglrbcu6WhwCLzHb90E94JI3TD3ricC3YGbUqIi9Xg0w==", + "@graphql-tools/delegate": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/@graphql-tools/delegate/-/delegate-8.5.1.tgz", + "integrity": "sha512-/YPmVxitt57F8sH50pnfXASzOOjEfaUDkX48eF5q6f16+JBncej2zeu+Zm2c68q8MbIxhPlEGfpd0QZeqTvAxw==", "requires": { - "@ardatan/aggregate-error": "0.0.6", - "camel-case": "4.1.2", - "tslib": "~2.2.0" - }, - "dependencies": { - "tslib": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", - "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==" - } + "@graphql-tools/batch-execute": "^8.3.2", + "@graphql-tools/schema": "^8.3.2", + "@graphql-tools/utils": "^8.6.2", + "dataloader": "2.0.0", + "graphql-executor": "0.0.18", + "tslib": "~2.3.0", + "value-or-promise": "1.0.11" + } + }, + "@graphql-tools/merge": { + "version": "8.2.3", + "resolved": "https://registry.npmjs.org/@graphql-tools/merge/-/merge-8.2.3.tgz", + "integrity": "sha512-XCSmL6/Xg8259OTWNp69B57CPWiVL69kB7pposFrufG/zaAlI9BS68dgzrxmmSqZV5ZHU4r/6Tbf6fwnEJGiSw==", + "requires": { + "@graphql-tools/utils": "^8.6.2", + "tslib": "~2.3.0" + } + }, + "@graphql-tools/schema": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-8.3.2.tgz", + "integrity": "sha512-77feSmIuHdoxMXRbRyxE8rEziKesd/AcqKV6fmxe7Zt+PgIQITxNDew2XJJg7qFTMNM43W77Ia6njUSBxNOkwg==", + "requires": { + "@graphql-tools/merge": "^8.2.3", + "@graphql-tools/utils": "^8.6.2", + "tslib": "~2.3.0", + "value-or-promise": "1.0.11" + } + }, + "@graphql-tools/utils": { + "version": "8.6.2", + "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-8.6.2.tgz", + "integrity": "sha512-x1DG0cJgpJtImUlNE780B/dfp8pxvVxOD6UeykFH5rHes26S4kGokbgU8F1IgrJ1vAPm/OVBHtd2kicTsPfwdA==", + "requires": { + "tslib": "~2.3.0" } }, "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==", + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/apollo-upload-client/-/apollo-upload-client-17.0.0.tgz", + "integrity": "sha512-pue33bWVbdlXAGFPkgz53TTmxVMrKeQr0mdRcftNY+PoHIdbGZD0hoaXHvO6OePJAkFz7OiCFUf98p1G/9+Ykw==", "requires": { - "@apollo/client": "^3.1.5", - "@babel/runtime": "^7.11.2", - "extract-files": "^9.0.0" + "extract-files": "^11.0.0" } + }, + "form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, + "node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "requires": { + "whatwg-url": "^5.0.0" + } + }, + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + }, + "value-or-promise": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/value-or-promise/-/value-or-promise-1.0.11.tgz", + "integrity": "sha512-41BrgH+dIbCFXClcSapVs5M6GkENd3gQOJpEfPDNa71LsUGMXDL0jMWpI/Rh7WhX+Aalfz2TTS3Zt5pUsbnhLg==" } } }, @@ -1387,9 +1473,9 @@ } }, "@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==" + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.1.1.tgz", + "integrity": "sha512-NQ17ii0rK1b34VZonlmT2QMJFI70m0TRwbknO/ihlbatXyaktDhN/98vBiUU6kNBPljqGqyIrl2T4nY2RpFANg==" }, "@istanbuljs/load-nyc-config": { "version": "1.1.0", @@ -1703,6 +1789,15 @@ "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", "dev": true + }, + "node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dev": true, + "requires": { + "whatwg-url": "^5.0.0" + } } } }, @@ -1738,10 +1833,15 @@ "@octokit/openapi-types": "^11.2.0" } }, + "@panva/asn1.js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@panva/asn1.js/-/asn1.js-1.0.0.tgz", + "integrity": "sha512-UdkG3mLEqXgnlKsWanWcgb6dOjUzJ+XC5f+aWw30qrtjxeNUSfKX1cd5FBzOaXQumoe9nIqeZUvrRJS03HCCtw==" + }, "@parse/fs-files-adapter": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@parse/fs-files-adapter/-/fs-files-adapter-1.2.0.tgz", - "integrity": "sha512-kr7Ti2eYOm14p05S86yriJdMtawL6qln3Dn5eekrwY14ih4jrjH/E+QlEpBUSBzN64fluFxciFOyjdbwDGWsGw==" + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@parse/fs-files-adapter/-/fs-files-adapter-1.2.1.tgz", + "integrity": "sha512-jUbmlvql9+5Mz8Q6KSk1jH823MVerhOYK1svayYpF03v75OtDn3p+mAoFvPS5UpRln1kT6BlBnLfw4Hv08SD5Q==" }, "@parse/minami": { "version": "1.0.0", @@ -1750,9 +1850,9 @@ "dev": true }, "@parse/node-apn": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@parse/node-apn/-/node-apn-4.1.1.tgz", - "integrity": "sha512-stWlQE95w5T0vkVYscoq/S3eXPQ1qzdQbKKQ8GAdw4CSNxRWLWgOH50byUR30thnQ93RshLCH5ROkvXMqzzLtw==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@parse/node-apn/-/node-apn-5.1.0.tgz", + "integrity": "sha512-WT3iVwr1Y/Jf4nq4RGNwBdLwm3gTodsb+g3IY98MPSJ7LCNf+R81Nj/nQO5r/twJfN1v5B8cAgfvPGs2rPelvg==", "requires": { "debug": "4.3.2", "jsonwebtoken": "8.5.1", @@ -1786,11 +1886,11 @@ } }, "@parse/push-adapter": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/@parse/push-adapter/-/push-adapter-3.4.1.tgz", - "integrity": "sha512-iev69kbwhXbez5nfEwB2GkCWBLUmRlImQTmHPpLnHBJfATYKyXf/H41WQhBPuwwScZBVp9ABsIsjjKy8iKg3fw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@parse/push-adapter/-/push-adapter-4.1.0.tgz", + "integrity": "sha512-8SOU4zgIr3+wn6Hbge4X/zAYAcJR7puJ3aY2ri+8fqMARgBria4JkIeAyKaTG/mUMHw6Qy5DpYYRe0LjImjZNw==", "requires": { - "@parse/node-apn": "4.1.1", + "@parse/node-apn": "5.1.0", "@parse/node-gcm": "1.0.2", "npmlog": "4.1.2", "parse": "3.3.0" @@ -2153,6 +2253,12 @@ "universalify": "^2.0.0" } }, + "mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -2305,7 +2411,8 @@ "@tootallnate/once": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", - "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==" + "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", + "dev": true }, "@types/accepts": { "version": "1.3.5", @@ -2556,6 +2663,20 @@ "@types/node": "*" } }, + "@types/webidl-conversions": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-6.1.1.tgz", + "integrity": "sha512-XAahCdThVuCFDQLT7R7Pk/vqeObFNL3YqRyFZg+AqAP/W1/w3xHaIxuW7WszQqTbIBOPRcItYJIou3i/mppu3Q==" + }, + "@types/whatwg-url": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.1.tgz", + "integrity": "sha512-2YubE1sjj5ifxievI5Ge1sckb9k/Er66HyR2c+3+I6VDUUg1TLPdYYTEbQ+DjRkS4nTxMJhgWfSfMRD2sl2EYQ==", + "requires": { + "@types/node": "*", + "@types/webidl-conversions": "*" + } + }, "@types/ws": { "version": "7.4.7", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.4.7.tgz", @@ -2579,11 +2700,6 @@ "integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==", "dev": true }, - "@types/zen-observable": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/@types/zen-observable/-/zen-observable-0.8.3.tgz", - "integrity": "sha512-fbF6oTd4sGGy0xjHPKAt+eS2CrxJ3+6gQ3FGcBoIJR2TLAyCkCyI8JqZNy+FeON0AhVgNJoUumVoZQjBFUqHkw==" - }, "@typescript-eslint/types": { "version": "4.31.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.31.0.tgz", @@ -2743,6 +2859,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, "requires": { "debug": "4" }, @@ -2751,6 +2868,7 @@ "version": "4.3.2", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "dev": true, "requires": { "ms": "2.1.2" } @@ -2758,7 +2876,8 @@ "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true } } }, @@ -2910,24 +3029,6 @@ } } }, - "apollo-cache": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/apollo-cache/-/apollo-cache-1.3.5.tgz", - "integrity": "sha512-1XoDy8kJnyWY/i/+gLTEbYLnoiVtS8y7ikBr/IfmML4Qb+CM7dEEbIUOjnY716WqmZ/UpXIxTfJsY7rMcqiCXA==", - "dev": true, - "requires": { - "apollo-utilities": "^1.3.4", - "tslib": "^1.10.0" - }, - "dependencies": { - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - } - } - }, "apollo-cache-control": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/apollo-cache-control/-/apollo-cache-control-0.14.0.tgz", @@ -2937,76 +3038,6 @@ "apollo-server-plugin-base": "^0.13.0" } }, - "apollo-cache-inmemory": { - "version": "1.6.6", - "resolved": "https://registry.npmjs.org/apollo-cache-inmemory/-/apollo-cache-inmemory-1.6.6.tgz", - "integrity": "sha512-L8pToTW/+Xru2FFAhkZ1OA9q4V4nuvfoPecBM34DecAugUZEBhI2Hmpgnzq2hTKZ60LAMrlqiASm0aqAY6F8/A==", - "dev": true, - "requires": { - "apollo-cache": "^1.3.5", - "apollo-utilities": "^1.3.4", - "optimism": "^0.10.0", - "ts-invariant": "^0.4.0", - "tslib": "^1.10.0" - }, - "dependencies": { - "@wry/context": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/@wry/context/-/context-0.4.4.tgz", - "integrity": "sha512-LrKVLove/zw6h2Md/KZyWxIkFM6AoyKp71OqpH9Hiip1csjPVoD3tPxlbQUNxEnHENks3UGgNpSBCAfq9KWuag==", - "dev": true, - "requires": { - "@types/node": ">=6", - "tslib": "^1.9.3" - } - }, - "optimism": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/optimism/-/optimism-0.10.3.tgz", - "integrity": "sha512-9A5pqGoQk49H6Vhjb9kPgAeeECfUDF6aIICbMDL23kDLStBn1MWk3YvcZ4xWF9CsSf6XEgvRLkXy4xof/56vVw==", - "dev": true, - "requires": { - "@wry/context": "^0.4.0" - } - }, - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - } - } - }, - "apollo-client": { - "version": "2.6.10", - "resolved": "https://registry.npmjs.org/apollo-client/-/apollo-client-2.6.10.tgz", - "integrity": "sha512-jiPlMTN6/5CjZpJOkGeUV0mb4zxx33uXWdj/xQCfAMkuNAC3HN7CvYDyMHHEzmcQ5GV12LszWoQ/VlxET24CtA==", - "dev": true, - "requires": { - "@types/zen-observable": "^0.8.0", - "apollo-cache": "1.3.5", - "apollo-link": "^1.0.0", - "apollo-utilities": "1.3.4", - "symbol-observable": "^1.0.2", - "ts-invariant": "^0.4.0", - "tslib": "^1.10.0", - "zen-observable": "^0.8.0" - }, - "dependencies": { - "symbol-observable": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", - "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==", - "dev": true - }, - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - } - } - }, "apollo-datasource": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/apollo-datasource/-/apollo-datasource-0.9.0.tgz", @@ -3044,62 +3075,6 @@ } } }, - "apollo-link-http": { - "version": "1.5.17", - "resolved": "https://registry.npmjs.org/apollo-link-http/-/apollo-link-http-1.5.17.tgz", - "integrity": "sha512-uWcqAotbwDEU/9+Dm9e1/clO7hTB2kQ/94JYcGouBVLjoKmTeJTUPQKcJGpPwUjZcSqgYicbFqQSoJIW0yrFvg==", - "dev": true, - "requires": { - "apollo-link": "^1.2.14", - "apollo-link-http-common": "^0.2.16", - "tslib": "^1.9.3" - }, - "dependencies": { - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - } - } - }, - "apollo-link-http-common": { - "version": "0.2.16", - "resolved": "https://registry.npmjs.org/apollo-link-http-common/-/apollo-link-http-common-0.2.16.tgz", - "integrity": "sha512-2tIhOIrnaF4UbQHf7kjeQA/EmSorB7+HyJIIrUjJOKBgnXwuexi8aMecRlqTIDWcyVXCeqLhUnztMa6bOH/jTg==", - "dev": true, - "requires": { - "apollo-link": "^1.2.14", - "ts-invariant": "^0.4.0", - "tslib": "^1.9.3" - }, - "dependencies": { - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - } - } - }, - "apollo-link-ws": { - "version": "1.0.20", - "resolved": "https://registry.npmjs.org/apollo-link-ws/-/apollo-link-ws-1.0.20.tgz", - "integrity": "sha512-mjSFPlQxmoLArpHBeUb2Xj+2HDYeTaJqFGOqQ+I8NVJxgL9lJe84PDWcPah/yMLv3rB7QgBDSuZ0xoRFBPlySw==", - "dev": true, - "requires": { - "apollo-link": "^1.2.14", - "tslib": "^1.9.3" - }, - "dependencies": { - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - } - } - }, "apollo-reporting-protobuf": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/apollo-reporting-protobuf/-/apollo-reporting-protobuf-0.8.0.tgz", @@ -3207,6 +3182,16 @@ "requires": { "node-fetch": "^2.6.1", "util.promisify": "^1.0.0" + }, + "dependencies": { + "node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "requires": { + "whatwg-url": "^5.0.0" + } + } } }, "apollo-server-errors": { @@ -3300,23 +3285,12 @@ } }, "apollo-upload-client": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/apollo-upload-client/-/apollo-upload-client-13.0.0.tgz", - "integrity": "sha512-lJ9/bk1BH1lD15WhWRha2J3+LrXrPIX5LP5EwiOUHv8PCORp4EUrcujrA3rI5hZeZygrTX8bshcuMdpqpSrvtA==", + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/apollo-upload-client/-/apollo-upload-client-16.0.0.tgz", + "integrity": "sha512-aLhYucyA0T8aBEQ5g+p13qnR9RUyL8xqb8FSZ7e/Kw2KUOsotLUlFluLobqaE7JSUFwc6sKfXIcwB7y4yEjbZg==", "dev": true, "requires": { - "@babel/runtime": "^7.9.2", - "apollo-link": "^1.2.12", - "apollo-link-http-common": "^0.2.14", - "extract-files": "^8.0.0" - }, - "dependencies": { - "extract-files": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/extract-files/-/extract-files-8.1.0.tgz", - "integrity": "sha512-PTGtfthZK79WUMk+avLmwx3NGdU8+iVFXC2NMGxKsn0MnihOG2lvumj+AZo8CTwTrwjXDgZ5tztbRlEdRjBonQ==", - "dev": true - } + "extract-files": "^11.0.0" } }, "apollo-utilities": { @@ -3484,7 +3458,8 @@ "async": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/async/-/async-3.2.1.tgz", - "integrity": "sha512-XdD5lRO/87udXCMC9meWdYiR+Nq6ZjUfXidViUZGu2F1MO4T3XwZ1et0hb2++BgLfhyJwy44BGB/yx80ABx8hg==" + "integrity": "sha512-XdD5lRO/87udXCMC9meWdYiR+Nq6ZjUfXidViUZGu2F1MO4T3XwZ1et0hb2++BgLfhyJwy44BGB/yx80ABx8hg==", + "dev": true }, "async-each": { "version": "1.0.3", @@ -3529,14 +3504,6 @@ "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==" }, - "axios": { - "version": "0.21.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", - "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", - "requires": { - "follow-redirects": "^1.14.0" - } - }, "babel-eslint": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.1.0.tgz", @@ -3649,8 +3616,7 @@ "base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" }, "bcrypt-nodejs": { "version": "0.0.3", @@ -3698,6 +3664,7 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/bl/-/bl-2.2.1.tgz", "integrity": "sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g==", + "dev": true, "requires": { "readable-stream": "^2.3.5", "safe-buffer": "^5.1.1" @@ -3710,20 +3677,20 @@ "dev": true }, "body-parser": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", - "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.1.tgz", + "integrity": "sha512-8ljfQi5eBk8EJfECMrgqNGWPEY5jWP+1IzkzkGdFFEwFQZZyaZ21UqdaHktgiMlH0xLHqIFtE/u2OYE5dOtViA==", "requires": { - "bytes": "3.1.0", + "bytes": "3.1.1", "content-type": "~1.0.4", "debug": "2.6.9", "depd": "~1.1.2", - "http-errors": "1.7.2", + "http-errors": "1.8.1", "iconv-lite": "0.4.24", "on-finished": "~2.3.0", - "qs": "6.7.0", - "raw-body": "2.4.0", - "type-is": "~1.6.17" + "qs": "6.9.6", + "raw-body": "2.4.2", + "type-is": "~1.6.18" }, "dependencies": { "debug": { @@ -3735,31 +3702,26 @@ } }, "http-errors": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", - "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", "requires": { "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.1", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" + "toidentifier": "1.0.1" } }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - }, "qs": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" + "version": "6.9.6", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.6.tgz", + "integrity": "sha512-TIRk4aqYLNoJUbd+g2lEdz5kLWIuTMRagAXxl78Q0RiVjAOugHmeKNGdd3cwo/ktpf9aL9epCfFqWDEKysUlLQ==" }, - "setprototypeof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + "toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" } } }, @@ -3839,13 +3801,13 @@ "bson": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.6.tgz", - "integrity": "sha512-EvVNVeGo4tHxwi8L6bPj3y3itEvStdwvvlojVxxbyYfoaxJ6keLgrTuKdyfEAszFK+H3olzBuafE0yoh0D1gdg==" + "integrity": "sha512-EvVNVeGo4tHxwi8L6bPj3y3itEvStdwvvlojVxxbyYfoaxJ6keLgrTuKdyfEAszFK+H3olzBuafE0yoh0D1gdg==", + "dev": true }, "buffer": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, "requires": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" @@ -3898,9 +3860,9 @@ } }, "bytes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.1.tgz", + "integrity": "sha512-dWe4nWO/ruEOY7HkUJ5gFt1DCFV9zPRoJr8pV0/ASQermOZjtq8jMjOprC0Kd10GLN+l7xaUPvxzJFWtxGu8Fg==" }, "cache-base": { "version": "1.0.1", @@ -4004,15 +3966,6 @@ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true }, - "camel-case": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", - "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", - "requires": { - "pascal-case": "^3.1.2", - "tslib": "^2.0.3" - } - }, "camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", @@ -4557,12 +4510,12 @@ } }, "color": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/color/-/color-3.0.0.tgz", - "integrity": "sha512-jCpd5+s0s0t7p3pHQKpnJ0TpQKKdleP71LWcA0aqiljpiuAkOSUFN/dyH8ZwF0hRmFlrIuRhufds1QyEP9EB+w==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", + "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", "requires": { - "color-convert": "^1.9.1", - "color-string": "^1.5.2" + "color-convert": "^1.9.3", + "color-string": "^1.6.0" } }, "color-convert": { @@ -4579,9 +4532,9 @@ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, "color-string": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.6.0.tgz", - "integrity": "sha512-c/hGS+kRWJutUBEngKKmk4iH3sD59MBkoxVapS/0wgpCz2u7XsNloxknyvBhzwEs1IbV36D9PwqLPJ2DTu3vMA==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.0.tgz", + "integrity": "sha512-9Mrz2AQLefkH1UvASKj6v6hj/7eWgjnT/cVsR8CumieLoT+g900exWeNogqtweI8dxloXN9BDQTYro1oWu/5CQ==", "requires": { "color-name": "^1.0.0", "simple-swizzle": "^0.2.2" @@ -4596,14 +4549,15 @@ "colors": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", - "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==" + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "dev": true }, "colorspace": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.2.tgz", - "integrity": "sha512-vt+OoIP2d76xLhjwbBaucYlNSpPsrJWPlBTtwCpQKIu6/CSMutyzX93O/Do0qzpH3YoHEes8YEFXyZ797rEhzQ==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz", + "integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==", "requires": { - "color": "3.0.x", + "color": "^3.1.3", "text-hex": "1.0.x" } }, @@ -4680,6 +4634,7 @@ "version": "0.5.3", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "dev": true, "requires": { "safe-buffer": "5.1.2" }, @@ -4687,7 +4642,8 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true } } }, @@ -4816,9 +4772,9 @@ } }, "cookie": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", - "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==" }, "cookie-signature": { "version": "1.0.6", @@ -4891,14 +4847,6 @@ "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": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -4962,6 +4910,12 @@ "assert-plus": "^1.0.0" } }, + "data-uri-to-buffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz", + "integrity": "sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==", + "dev": true + }, "dataloader": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/dataloader/-/dataloader-2.0.0.tgz", @@ -6436,16 +6390,16 @@ } }, "express": { - "version": "4.17.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", - "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.2.tgz", + "integrity": "sha512-oxlxJxcQlYwqPWKVJJtvQiwHgosH/LrLSPA+H4UxpyvSS6jC5aH+5MoHFM+KABgTOt0APue4w66Ha8jCUo9QGg==", "requires": { "accepts": "~1.3.7", "array-flatten": "1.1.1", - "body-parser": "1.19.0", - "content-disposition": "0.5.3", + "body-parser": "1.19.1", + "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.4.0", + "cookie": "0.4.1", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "~1.1.2", @@ -6459,19 +6413,27 @@ "on-finished": "~2.3.0", "parseurl": "~1.3.3", "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.5", - "qs": "6.7.0", + "proxy-addr": "~2.0.7", + "qs": "6.9.6", "range-parser": "~1.2.1", - "safe-buffer": "5.1.2", - "send": "0.17.1", - "serve-static": "1.14.1", - "setprototypeof": "1.1.1", + "safe-buffer": "5.2.1", + "send": "0.17.2", + "serve-static": "1.14.2", + "setprototypeof": "1.2.0", "statuses": "~1.5.0", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" }, "dependencies": { + "content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "requires": { + "safe-buffer": "5.2.1" + } + }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -6481,19 +6443,9 @@ } }, "qs": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "setprototypeof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + "version": "6.9.6", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.6.tgz", + "integrity": "sha512-TIRk4aqYLNoJUbd+g2lEdz5kLWIuTMRagAXxl78Q0RiVjAOugHmeKNGdd3cwo/ktpf9aL9epCfFqWDEKysUlLQ==" } } }, @@ -6642,9 +6594,9 @@ } }, "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==" + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/extract-files/-/extract-files-11.0.0.tgz", + "integrity": "sha512-FuoE1qtbJ4bBVvv94CC7s0oTnKUGvQs+Rjf1L2SJFfS+HTVVjhPFtehPdQ0JiGPqVNfSSZvL5yzHHQq2Z4WNhQ==" }, "extsprintf": { "version": "1.4.0", @@ -6734,11 +6686,6 @@ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "dev": true }, - "fast-safe-stringify": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.8.tgz", - "integrity": "sha512-lXatBjf3WPjmWD6DpIZxkeSsCOwqI0maYMpgDlx8g4U2qi4lbjA9oH/HD2a87G+KfsUmo5WbJFmqBZlPxtptag==" - }, "fastq": { "version": "1.12.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.12.0.tgz", @@ -6762,6 +6709,16 @@ "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.1.tgz", "integrity": "sha512-MMMQ0ludy/nBs1/o0zVOiKTpG7qMbonKUzjJgQFEuvq6INZ1OraKPRAWkBq5vlKLOUMpmNYG1JoN3oDPUQ9m3Q==" }, + "fetch-blob": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.1.4.tgz", + "integrity": "sha512-Eq5Xv5+VlSrYWEqKrusxY1C3Hm/hjeAsCGVG3ft7pZahlUAChpGZT/Ms1WmSLnEAisEXszjzu/s+ce6HZB2VHA==", + "dev": true, + "requires": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + } + }, "fetch-node-website": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/fetch-node-website/-/fetch-node-website-5.0.3.tgz", @@ -6846,11 +6803,11 @@ } }, "file-stream-rotator": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/file-stream-rotator/-/file-stream-rotator-0.5.7.tgz", - "integrity": "sha512-VYb3HZ/GiAGUCrfeakO8Mp54YGswNUHvL7P09WQcXAJNSj3iQ5QraYSp3cIn1MUyw6uzfgN/EFOarCNa4JvUHQ==", + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/file-stream-rotator/-/file-stream-rotator-0.6.1.tgz", + "integrity": "sha512-u+dBid4PvZw17PmDeRcNOtCP9CCK/9lRN2w+r1xIS7yOL9JFrIBKTvrYsxT4P0pGtThYTn++QS5ChHaUov3+zQ==", "requires": { - "moment": "^2.11.2" + "moment": "^2.29.1" } }, "file-type": { @@ -7108,12 +7065,22 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.0.tgz", "integrity": "sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg==", + "dev": true, "requires": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "mime-types": "^2.1.12" } }, + "formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dev": true, + "requires": { + "fetch-blob": "^3.1.2" + } + }, "forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -7476,9 +7443,14 @@ "dev": true }, "graphql": { - "version": "15.6.0", - "resolved": "https://registry.npmjs.org/graphql/-/graphql-15.6.0.tgz", - "integrity": "sha512-WJR872Zlc9hckiEPhXgyUftXH48jp2EjO5tgBBOyNMRJZ9fviL2mJBD6CAysk6N5S0r9BTs09Qk39nnJBkvOXQ==" + "version": "15.7.1", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-15.7.1.tgz", + "integrity": "sha512-x34S6gC0/peBZnlK60zCJox/d45A7p6At9oN9EPA3qhoIAlR4LNZmXRLkICBckwwTMJzVdA8cx3QIQZMOl606A==" + }, + "graphql-executor": { + "version": "0.0.18", + "resolved": "https://registry.npmjs.org/graphql-executor/-/graphql-executor-0.0.18.tgz", + "integrity": "sha512-upUSl7tfZCZ5dWG1XkOvpG70Yk3duZKcCoi/uJso4WxJVT6KIrcK4nZ4+2X/hzx46pL8wAukgYHY6iNmocRN+g==" }, "graphql-extensions": { "version": "0.15.0", @@ -7509,9 +7481,9 @@ } }, "graphql-tag": { - "version": "2.12.5", - "resolved": "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.12.5.tgz", - "integrity": "sha512-5xNhP4063d16Pz3HBtKprutsPrmHZi5IdUGOWRxA2B6VF7BIRGOHZ5WQvDmJXZuPcBg7rYwaFxvQYjqkSdR3TQ==", + "version": "2.12.6", + "resolved": "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.12.6.tgz", + "integrity": "sha512-FdSNcu2QQcWnM2VNvSCCDCVS5PpPqpzgFT8+GXzqJuoDd0CBncxCY278u4mhRO7tMgo2JjgJA5aZ+nWSQ/Z+xg==", "requires": { "tslib": "^2.1.0" }, @@ -7814,6 +7786,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "dev": true, "requires": { "@tootallnate/once": "1", "agent-base": "6", @@ -7824,6 +7797,7 @@ "version": "4.3.2", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "dev": true, "requires": { "ms": "2.1.2" } @@ -7831,7 +7805,8 @@ "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true } } }, @@ -7849,6 +7824,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "dev": true, "requires": { "agent-base": "6", "debug": "4" @@ -7858,6 +7834,7 @@ "version": "4.3.2", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "dev": true, "requires": { "ms": "2.1.2" } @@ -7865,7 +7842,8 @@ "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true } } }, @@ -7966,8 +7944,7 @@ "ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" }, "ignore": { "version": "4.0.6", @@ -8179,6 +8156,11 @@ "loose-envify": "^1.0.0" } }, + "ip": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", + "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=" + }, "ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -8889,6 +8871,14 @@ } } }, + "jose": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/jose/-/jose-2.0.5.tgz", + "integrity": "sha512-BAiDNeDKTMgk4tvD0BbxJ8xHEHBZgpeRZ1zGPPsitSyMgjoMWiLGYAE7H7NpP5h0lPppQajQs871E8NHUrzVPA==", + "requires": { + "@panva/asn1.js": "^1.0.0" + } + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -9091,41 +9081,29 @@ } }, "jwks-rsa": { - "version": "1.12.3", - "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-1.12.3.tgz", - "integrity": "sha512-cFipFDeYYaO9FhhYJcZWX/IyZgc0+g316rcHnDpT2dNRNIE/lMOmWKKqp09TkJoYlNFzrEVODsR4GgXJMgWhnA==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-2.0.5.tgz", + "integrity": "sha512-fliHfsiBRzEU0nXzSvwnh0hynzGB0WihF+CinKbSRlaqRxbqqKf2xbBPgwc8mzf18/WgwlG8e5eTpfSTBcU4DQ==", "requires": { "@types/express-jwt": "0.0.42", - "axios": "^0.21.1", - "debug": "^4.1.0", - "http-proxy-agent": "^4.0.1", - "https-proxy-agent": "^5.0.0", - "jsonwebtoken": "^8.5.1", + "debug": "^4.3.2", + "jose": "^2.0.5", "limiter": "^1.1.5", - "lru-memoizer": "^2.1.2", - "ms": "^2.1.2", - "proxy-from-env": "^1.1.0" + "lru-memoizer": "^2.1.4" }, "dependencies": { "debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", "requires": { "ms": "2.1.2" - }, - "dependencies": { - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - } } }, "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" } } }, @@ -9794,14 +9772,14 @@ } }, "logform": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/logform/-/logform-2.2.0.tgz", - "integrity": "sha512-N0qPlqfypFx7UHNn4B3lzS/b0uLqt2hmuoa+PpuXNYgozdJYAyauF5Ky0BWVjrxDlMWiT3qN4zPq3vVAfZy7Yg==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.4.0.tgz", + "integrity": "sha512-CPSJw4ftjf517EhXZGGvTHHkYobo7ZCc0kvwUoOYcjfR2UVrI66RHj8MCrfAdEitdmFqbu2BYdYs8FHHZSb6iw==", "requires": { - "colors": "^1.2.1", - "fast-safe-stringify": "^2.0.4", + "@colors/colors": "1.5.0", "fecha": "^4.2.0", "ms": "^2.1.1", + "safe-stable-stringify": "^2.3.1", "triple-beam": "^1.3.0" }, "dependencies": { @@ -9845,18 +9823,11 @@ "dev": true }, "lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "requires": { - "yallist": "^3.0.2" - }, - "dependencies": { - "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" - } + "yallist": "^4.0.0" } }, "lru-memoizer": { @@ -10253,9 +10224,9 @@ } }, "mime": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz", - "integrity": "sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==" }, "mime-db": { "version": "1.49.0", @@ -10412,16 +10383,63 @@ "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==" }, "mongodb": { - "version": "3.6.11", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.6.11.tgz", - "integrity": "sha512-4Y4lTFHDHZZdgMaHmojtNAlqkvddX2QQBEN0K//GzxhGwlI9tZ9R0vhbjr1Decw+TF7qK0ZLjQT292XgHRRQgw==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.3.1.tgz", + "integrity": "sha512-sNa8APSIk+r4x31ZwctKjuPSaeKuvUeNb/fu/3B6dRM02HpEgig7hTHM8A/PJQTlxuC/KFWlDlQjhsk/S43tBg==", "requires": { - "bl": "^2.2.1", - "bson": "^1.1.4", - "denque": "^1.4.1", - "optional-require": "^1.0.3", - "safe-buffer": "^5.1.2", - "saslprep": "^1.0.0" + "bson": "^4.6.1", + "denque": "^2.0.1", + "mongodb-connection-string-url": "^2.4.1", + "saslprep": "^1.0.3", + "socks": "^2.6.1" + }, + "dependencies": { + "bson": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/bson/-/bson-4.6.1.tgz", + "integrity": "sha512-I1LQ7Hz5zgwR4QquilLNZwbhPw0Apx7i7X9kGMBTsqPdml/03Q9NBtD9nt/19ahjlphktQImrnderxqpzeVDjw==", + "requires": { + "buffer": "^5.6.0" + } + }, + "denque": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.0.1.tgz", + "integrity": "sha512-tfiWc6BQLXNLpNiR5iGd0Ocu3P3VpxfzFiqubLgMfhfOw9WyvgJBd46CClNn9k3qfbjvT//0cf7AlYRX/OslMQ==" + } + } + }, + "mongodb-connection-string-url": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.4.1.tgz", + "integrity": "sha512-d5Kd2bVsKcSA7YI/yo57fSTtMwRQdFkvc5IZwod1RRxJtECeWPPSo7zqcUGJELifRA//Igs4spVtYAmvFCatug==", + "requires": { + "@types/whatwg-url": "^8.2.1", + "whatwg-url": "^11.0.0" + }, + "dependencies": { + "tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "requires": { + "punycode": "^2.1.1" + } + }, + "webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==" + }, + "whatwg-url": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "requires": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + } + } } }, "mongodb-core": { @@ -10501,6 +10519,15 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true + }, + "node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dev": true, + "requires": { + "whatwg-url": "^5.0.0" + } } } }, @@ -10535,6 +10562,20 @@ "ms": "2.1.2" } }, + "mongodb": { + "version": "3.7.3", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.7.3.tgz", + "integrity": "sha512-Psm+g3/wHXhjBEktkxXsFMZvd3nemI0r3IPsE0bU+4//PnvNWKkzhZcEsbPcYiWqe8XqXJJEg4Tgtr7Raw67Yw==", + "dev": true, + "requires": { + "bl": "^2.2.1", + "bson": "^1.1.4", + "denque": "^1.4.1", + "optional-require": "^1.1.8", + "safe-buffer": "^5.1.2", + "saslprep": "^1.0.0" + } + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -10703,9 +10744,9 @@ "optional": true }, "nanoid": { - "version": "3.1.25", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.25.tgz", - "integrity": "sha512-rdwtIXaXCLFAQbnfqDRnI6jaRHp9fTcYBjtFKE8eezcZ7LuLjhUaQGNeMXf1HmRoCH32CLz6XwX0TtxEOS/A3Q==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.2.0.tgz", + "integrity": "sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA==", "dev": true }, "nanomatch": { @@ -10771,6 +10812,12 @@ "tslib": "^2.0.3" } }, + "node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "dev": true + }, "node-emoji": { "version": "1.11.0", "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz", @@ -10781,9 +10828,15 @@ } }, "node-fetch": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", - "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.1.1.tgz", + "integrity": "sha512-SMk+vKgU77PYotRdWzqZGTZeuFKlsJ0hu4KPviQKkfY+N3vn2MIzr0rvpnYpR8MtB3IEuhlEcuOLbGvLRlA+yg==", + "dev": true, + "requires": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.3", + "formdata-polyfill": "^4.0.10" + } }, "node-forge": { "version": "0.10.0", @@ -13235,9 +13288,10 @@ } }, "optional-require": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/optional-require/-/optional-require-1.1.7.tgz", - "integrity": "sha512-cIeRZocXsZnZYn+SevbtSqNlLbeoS4mLzuNn4fvXRMDRNhTGg0sxuKXl0FnZCtnew85LorNxIbZp5OeliILhMw==", + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/optional-require/-/optional-require-1.1.8.tgz", + "integrity": "sha512-jq83qaUb0wNg9Krv1c5OQ+58EK+vHde6aBPzLvPPqJm89UQWsvSuFy9X/OSNJnFeSOKo7btE0n8Nl2+nE+z5nA==", + "dev": true, "requires": { "require-at": "^1.0.6" } @@ -13511,37 +13565,54 @@ } }, "parse": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/parse/-/parse-3.3.1.tgz", - "integrity": "sha512-jrb8tpeanh49lIXuQYbaJoMzywX9YiBtM17aCvYGfaHYJipSTHABA774t8IZap+F8Pb4GgZ0fM4ObfiuO4395A==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/parse/-/parse-3.4.1.tgz", + "integrity": "sha512-XTMaHfcOwAOiWLraNtPbCzR8o94ZWjOfaZDMM2jZ1ZDB5twtK1B82lPa+N197efZhcQ+QFbW/eDJsBybD48aSQ==", "requires": { - "@babel/runtime": "7.14.8", - "@babel/runtime-corejs3": "7.14.6", + "@babel/runtime": "7.15.4", + "@babel/runtime-corejs3": "7.14.7", "crypto-js": "4.1.1", - "idb-keyval": "5.0.6", + "idb-keyval": "6.0.3", "react-native-crypto-js": "1.0.0", "uuid": "3.4.0", - "ws": "7.5.0", + "ws": "7.5.1", "xmlhttprequest": "1.8.0" }, "dependencies": { "@babel/runtime": { - "version": "7.14.8", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.8.tgz", - "integrity": "sha512-twj3L8Og5SaCRCErB4x4ajbvBIVV77CGeFglHpeg5WC5FF8TZzBWXtTJ4MqaD9QszLYTtr+IsaAL2rEUevb+eg==", + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.15.4.tgz", + "integrity": "sha512-99catp6bHCaxr4sJ/DbTGgHS4+Rs2RVd2g7iOap6SLGPDknRK9ztKNsE/Fg6QhSeh1FGE5f6gHGQmvvn3I3xhw==", "requires": { "regenerator-runtime": "^0.13.4" } }, + "@babel/runtime-corejs3": { + "version": "7.14.7", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.14.7.tgz", + "integrity": "sha512-Wvzcw4mBYbTagyBVZpAJWI06auSIj033T/yNE0Zn1xcup83MieCddZA7ls3kme17L4NOGBrQ09Q+nKB41RLWBA==", + "requires": { + "core-js-pure": "^3.15.0", + "regenerator-runtime": "^0.13.4" + } + }, + "idb-keyval": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/idb-keyval/-/idb-keyval-6.0.3.tgz", + "integrity": "sha512-yh8V7CnE6EQMu9YDwQXhRxwZh4nv+8xm/HV4ZqK4IiYFJBWYGjJuykADJbSP+F/GDXUBwCSSNn/14IpGL81TuA==", + "requires": { + "safari-14-idb-fix": "^3.0.0" + } + }, "uuid": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" }, "ws": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.0.tgz", - "integrity": "sha512-6ezXvzOZupqKj4jUqbQ9tXuJNo+BR2gU8fFRk3XCP3e0G6WT414u5ELe6Y0vtp7kmSJ3F7YWObSNr1ESsgi4vw==" + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.1.tgz", + "integrity": "sha512-2c6faOUH/nhoQN6abwMloF7Iyl0ZS2E9HGtsiLrWn0zOOMWlhtDmdf/uihDt6jnuCxgtwGBNy6Onsoy2s2O2Ow==" } } }, @@ -13680,9 +13751,9 @@ "integrity": "sha512-TVHxR/gf3MeJRvchgNHxsYsTCHQ+4wm3VIHSS19z8NC0+gioEhq1okDY1sm/TYbfoP6JLFx01s0ShvZ3puP/iQ==" }, "pg-promise": { - "version": "10.11.0", - "resolved": "https://registry.npmjs.org/pg-promise/-/pg-promise-10.11.0.tgz", - "integrity": "sha512-UntgHZNv+gpGJKhh+tzGSGHLkniKWV+ZQ8/SNdtvElsg9Aa7ZJ4Fgyl6pl2x0ZtJ7uFNy+OIq3Z+Ei6iplqTDQ==", + "version": "10.11.1", + "resolved": "https://registry.npmjs.org/pg-promise/-/pg-promise-10.11.1.tgz", + "integrity": "sha512-HAv32WSKf2m2RqHerW5RmANn/mcXIwWXbg/gOfGQcoS0SE+8iBi3Jj4JmoR4PNzSEozo/y/npy4e6F16psOItw==", "requires": { "assert-options": "0.7.0", "pg": "8.7.1", @@ -14031,13 +14102,13 @@ "dev": true }, "prop-types": { - "version": "15.7.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", - "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", "requires": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", - "react-is": "^16.8.1" + "react-is": "^16.13.1" } }, "proto-list": { @@ -14055,11 +14126,6 @@ "ipaddr.js": "1.9.1" } }, - "proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" - }, "ps-node": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/ps-node/-/ps-node-0.1.6.tgz", @@ -14129,37 +14195,32 @@ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" }, "raw-body": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", - "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.2.tgz", + "integrity": "sha512-RPMAFUJP19WIet/99ngh6Iv8fzAbqum4Li7AD6DtGaW2RpMB/11xDoalPiJMTbu6I3hkbMVkATvZrqb9EEqeeQ==", "requires": { - "bytes": "3.1.0", - "http-errors": "1.7.2", + "bytes": "3.1.1", + "http-errors": "1.8.1", "iconv-lite": "0.4.24", "unpipe": "1.0.0" }, "dependencies": { "http-errors": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", - "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", "requires": { "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.1", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" + "toidentifier": "1.0.1" } }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - }, - "setprototypeof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + "toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" } } }, @@ -14506,7 +14567,8 @@ "require-at": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/require-at/-/require-at-1.0.6.tgz", - "integrity": "sha512-7i1auJbMUrXEAZCOQ0VNJgmcT2VOKPRl2YGJwgpHpC9CE91Mv4/4UYIUm4chGJaI381ZDq1JUicFii64Hapd8g==" + "integrity": "sha512-7i1auJbMUrXEAZCOQ0VNJgmcT2VOKPRl2YGJwgpHpC9CE91Mv4/4UYIUm4chGJaI381ZDq1JUicFii64Hapd8g==", + "dev": true }, "require-directory": { "version": "2.1.1", @@ -14676,6 +14738,11 @@ } } }, + "safari-14-idb-fix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/safari-14-idb-fix/-/safari-14-idb-fix-3.0.0.tgz", + "integrity": "sha512-eBNFLob4PMq8JA1dGyFn6G97q3/WzNtFK4RnzT1fnLq+9RyrGknzYiM/9B12MnKAxuj1IXr7UKYtTNtjyKMBog==" + }, "safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -14691,6 +14758,11 @@ "ret": "~0.1.10" } }, + "safe-stable-stringify": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.3.1.tgz", + "integrity": "sha512-kYBSfT+troD9cDA85VDnHZ1rpHC50O0g1e6WlGHVCz/g+JS+9WKLj+XwFYyR8UbrZN8ll9HUpDAAddY58MGisg==" + }, "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -15057,9 +15129,9 @@ "dev": true }, "send": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", - "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "version": "0.17.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz", + "integrity": "sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==", "requires": { "debug": "2.6.9", "depd": "~1.1.2", @@ -15068,9 +15140,9 @@ "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "0.5.2", - "http-errors": "~1.7.2", + "http-errors": "1.8.1", "mime": "1.6.0", - "ms": "2.1.1", + "ms": "2.1.3", "on-finished": "~2.3.0", "range-parser": "~1.2.1", "statuses": "~1.5.0" @@ -15092,15 +15164,15 @@ } }, "http-errors": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", - "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", "requires": { "depd": "~1.1.2", "inherits": "2.0.4", - "setprototypeof": "1.1.1", + "setprototypeof": "1.2.0", "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" + "toidentifier": "1.0.1" } }, "mime": { @@ -15109,26 +15181,26 @@ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" }, "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, - "setprototypeof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + "toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" } } }, "serve-static": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", - "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.2.tgz", + "integrity": "sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ==", "requires": { "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.17.1" + "send": "0.17.2" } }, "set-blocking": { @@ -15260,6 +15332,11 @@ } } }, + "smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==" + }, "snapdragon": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", @@ -15394,6 +15471,15 @@ } } }, + "socks": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.6.1.tgz", + "integrity": "sha512-kLQ9N5ucj8uIcxrDwjm0Jsqk06xdpBjGNQtpXy4Q8/QY2k+fY7nZH8CARy+hkbG+SGAovmzzuauCpBlb8FrnBA==", + "requires": { + "ip": "^1.1.5", + "smart-buffer": "^4.1.0" + } + }, "sort-keys": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", @@ -15799,9 +15885,9 @@ } }, "subscriptions-transport-ws": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/subscriptions-transport-ws/-/subscriptions-transport-ws-0.10.0.tgz", - "integrity": "sha512-k28LhLn3abJ1mowFW+LP4QGggE0e3hrk55zXbMHyAeZkCUYtC0owepiwqMD3zX8DglQVaxnhE760pESrNSEzpg==", + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/subscriptions-transport-ws/-/subscriptions-transport-ws-0.11.0.tgz", + "integrity": "sha512-8D4C6DIH5tGiAIpp5I0wD/xRlNiZAPGHygzCe7VzyzUoxHtawzjNAY9SUTXU05/EY2NMY9/9GF0ycizkXr1CWQ==", "requires": { "backo2": "^1.0.2", "eventemitter3": "^3.1.0", @@ -15816,9 +15902,9 @@ "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==" }, "ws": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.5.tgz", - "integrity": "sha512-BAkMFcAzl8as1G/hArkxOxq3G7pjUqQ3gzYbLL0/5zNkph70e+lCoxBGnm6AW1+/aiNeV4fnKqZ8m4GZewmH2w==" + "version": "7.5.6", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.6.tgz", + "integrity": "sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA==" } } }, @@ -16181,6 +16267,11 @@ } } }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" + }, "traverse": { "version": "0.6.6", "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.6.tgz", @@ -16623,6 +16714,26 @@ "defaults": "^1.0.3" } }, + "web-streams-polyfill": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.0.tgz", + "integrity": "sha512-EqPmREeOzttaLRm5HS7io98goBgZ7IVz79aDvqjD0kYXLtFZTc0T/U6wHTPKyIjb+MdN7DFIIX6hgdBEpWmfPA==", + "dev": true + }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -16657,29 +16768,35 @@ "dev": true }, "wide-align": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", - "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", "requires": { - "string-width": "^1.0.2 || 2" + "string-width": "^1.0.2 || 2 || 3 || 4" } }, "winston": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/winston/-/winston-3.3.3.tgz", - "integrity": "sha512-oEXTISQnC8VlSAKf1KYSSd7J6IWuRPQqDdo8eoRNaYKLvwSb5+79Z3Yi1lrl6KDpU6/VWaxpakDAtb1oQ4n9aw==", + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.5.1.tgz", + "integrity": "sha512-tbRtVy+vsSSCLcZq/8nXZaOie/S2tPXPFt4be/Q3vI/WtYwm7rrwidxVw2GRa38FIXcJ1kUM6MOZ9Jmnk3F3UA==", "requires": { "@dabh/diagnostics": "^2.0.2", - "async": "^3.1.0", + "async": "^3.2.3", "is-stream": "^2.0.0", - "logform": "^2.2.0", + "logform": "^2.3.2", "one-time": "^1.0.0", "readable-stream": "^3.4.0", + "safe-stable-stringify": "^2.3.1", "stack-trace": "0.0.x", "triple-beam": "^1.3.0", - "winston-transport": "^4.4.0" + "winston-transport": "^4.4.2" }, "dependencies": { + "async": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.3.tgz", + "integrity": "sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==" + }, "readable-stream": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", @@ -16693,23 +16810,36 @@ } }, "winston-daily-rotate-file": { - "version": "4.5.5", - "resolved": "https://registry.npmjs.org/winston-daily-rotate-file/-/winston-daily-rotate-file-4.5.5.tgz", - "integrity": "sha512-ds0WahIjiDhKCiMXmY799pDBW+58ByqIBtUcsqr4oDoXrAI3Zn+hbgFdUxzMfqA93OG0mPLYVMiotqTgE/WeWQ==", + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/winston-daily-rotate-file/-/winston-daily-rotate-file-4.6.0.tgz", + "integrity": "sha512-mvpFb1LYmTvh/vz0dIS/aDCwEm0cvDa8D/tE4xWwdUYolD250wf+n0y1PZ2xr7fbvTLF/PQYqXtFIFrmog03Ow==", "requires": { - "file-stream-rotator": "^0.5.7", + "file-stream-rotator": "^0.6.1", "object-hash": "^2.0.1", "triple-beam": "^1.3.0", "winston-transport": "^4.4.0" } }, "winston-transport": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.4.0.tgz", - "integrity": "sha512-Lc7/p3GtqtqPBYYtS6KCN3c77/2QCev51DvcJKbkFPQNoj1sinkGwLGFDxkXY9J6p9+EPnYs+D90uwbnaiURTw==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.5.0.tgz", + "integrity": "sha512-YpZzcUzBedhlTAfJg6vJDlyEai/IFMIVcaEZZyl3UXIl4gmqRpU7AE89AHLkbzLUsv0NVmw7ts+iztqKxxPW1Q==", "requires": { - "readable-stream": "^2.3.7", - "triple-beam": "^1.2.0" + "logform": "^2.3.2", + "readable-stream": "^3.6.0", + "triple-beam": "^1.3.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } } }, "word-wrap": { @@ -16820,9 +16950,9 @@ } }, "ws": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.2.tgz", - "integrity": "sha512-Q6B6H2oc8QY3llc3cB8kVmQ6pnJWVQbP7Q5algTcIxx7YEpc0oU4NBVHlztA7Ekzfhw2r0rPducMUiCGWKQRzw==" + "version": "8.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", + "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==" }, "xmlcreate": { "version": "2.0.3", diff --git a/package.json b/package.json index 2551ed49..567b8a95 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "parse-server", - "version": "5.0.0", + "version": "5.0.0-beta.10", "description": "An express module providing a Parse-compatible API server", "main": "lib/index.js", "repository": { @@ -20,45 +20,46 @@ "license": "BSD-3-Clause", "dependencies": { "@apollographql/graphql-playground-html": "1.6.29", - "@graphql-tools/links": "6.2.5", + "@graphql-tools/links": "8.2.2", + "@apollo/client": "3.5.8", "@graphql-tools/stitch": "6.2.4", "@graphql-tools/utils": "6.2.4", - "@parse/fs-files-adapter": "1.2.0", - "@parse/push-adapter": "3.4.1", + "@parse/fs-files-adapter": "1.2.1", + "@parse/push-adapter": "4.1.0", "apollo-server-express": "2.25.2", "bcryptjs": "2.4.3", - "body-parser": "1.19.0", + "body-parser": "1.19.1", "commander": "5.1.0", "cors": "2.8.5", "deepcopy": "2.1.0", - "express": "4.17.1", + "express": "4.17.2", "follow-redirects": "1.14.8", - "graphql": "15.6.0", + "graphql": "15.7.1", "graphql-list-fields": "2.0.2", "graphql-relay": "0.7.0", - "graphql-tag": "2.12.5", + "graphql-tag": "2.12.6", "graphql-upload": "11.0.0", "intersect": "1.0.1", "jsonwebtoken": "8.5.1", - "jwks-rsa": "1.12.3", + "jwks-rsa": "2.0.5", "ldapjs": "2.3.1", "lodash": "4.17.21", - "lru-cache": "5.1.1", - "mime": "2.5.2", - "mongodb": "3.6.11", + "lru-cache": "6.0.0", + "mime": "3.0.0", + "mongodb": "4.3.1", "mustache": "4.2.0", - "parse": "3.3.1", + "parse": "3.4.1", "pg-monitor": "1.4.1", - "pg-promise": "10.11.0", + "pg-promise": "10.11.1", "pluralize": "8.0.0", "redis": "3.1.2", "semver": "7.3.5", - "subscriptions-transport-ws": "0.10.0", + "subscriptions-transport-ws": "0.11.0", "tv4": "1.3.0", "uuid": "8.3.2", - "winston": "3.3.3", - "winston-daily-rotate-file": "4.5.5", - "ws": "8.2.2" + "winston": "3.5.1", + "winston-daily-rotate-file": "4.6.0", + "ws": "8.2.3" }, "devDependencies": { "@actions/core": "1.2.6", @@ -75,12 +76,7 @@ "@semantic-release/npm": "7.1.3", "@semantic-release/release-notes-generator": "9.0.3", "all-node-versions": "8.0.0", - "apollo-cache-inmemory": "1.6.6", - "apollo-client": "2.6.10", - "apollo-link": "1.2.14", - "apollo-link-http": "1.5.17", - "apollo-link-ws": "1.0.20", - "apollo-upload-client": "13.0.0", + "apollo-upload-client": "16.0.0", "apollo-utilities": "1.3.4", "babel-eslint": "10.1.0", "bcrypt-nodejs": "0.0.3", @@ -101,7 +97,7 @@ "mock-mail-adapter": "file:spec/dependencies/mock-mail-adapter", "mongodb-runner": "4.8.1", "mongodb-version-list": "1.0.0", - "node-fetch": "2.6.1", + "node-fetch": "3.1.1", "nyc": "15.1.0", "prettier": "2.0.5", "semantic-release": "17.4.6", @@ -122,12 +118,13 @@ "test:mongodb:4.0.27": "npm run test:mongodb --dbversion=4.0.27", "test:mongodb:4.2.17": "npm run test:mongodb --dbversion=4.2.17", "test:mongodb:4.4.10": "npm run test:mongodb --dbversion=4.4.10", + "test:mongodb:5.0.5": "npm run test:mongodb --dbversion=5.0.5", "posttest:mongodb": "mongodb-runner stop", - "pretest": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=4.4.10} MONGODB_TOPOLOGY=${MONGODB_TOPOLOGY:=standalone} MONGODB_STORAGE_ENGINE=${MONGODB_STORAGE_ENGINE:=wiredTiger} mongodb-runner start", - "testonly": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=4.4.10} MONGODB_TOPOLOGY=${MONGODB_TOPOLOGY:=standalone} MONGODB_STORAGE_ENGINE=${MONGODB_STORAGE_ENGINE:=wiredTiger} TESTING=1 jasmine", + "pretest": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=5.0.5} MONGODB_TOPOLOGY=${MONGODB_TOPOLOGY:=standalone} MONGODB_STORAGE_ENGINE=${MONGODB_STORAGE_ENGINE:=wiredTiger} mongodb-runner start", + "testonly": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=5.0.5} MONGODB_TOPOLOGY=${MONGODB_TOPOLOGY:=standalone} MONGODB_STORAGE_ENGINE=${MONGODB_STORAGE_ENGINE:=wiredTiger} TESTING=1 jasmine", "test": "npm run testonly", - "posttest": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=4.4.10} MONGODB_TOPOLOGY=${MONGODB_TOPOLOGY:=standalone} MONGODB_STORAGE_ENGINE=${MONGODB_STORAGE_ENGINE:=wiredTiger} mongodb-runner stop", - "coverage": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=4.4.10} MONGODB_TOPOLOGY=${MONGODB_TOPOLOGY:=standalone} MONGODB_STORAGE_ENGINE=${MONGODB_STORAGE_ENGINE:=wiredTiger} TESTING=1 nyc jasmine", + "posttest": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=5.0.5} MONGODB_TOPOLOGY=${MONGODB_TOPOLOGY:=standalone} MONGODB_STORAGE_ENGINE=${MONGODB_STORAGE_ENGINE:=wiredTiger} mongodb-runner stop", + "coverage": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=5.0.5} MONGODB_TOPOLOGY=${MONGODB_TOPOLOGY:=standalone} MONGODB_STORAGE_ENGINE=${MONGODB_STORAGE_ENGINE:=wiredTiger} TESTING=1 nyc jasmine", "start": "node ./bin/parse-server", "prettier": "prettier --write {src,spec}/{**/*,*}.js", "prepare": "npm run build", @@ -135,7 +132,7 @@ "madge:circular": "node_modules/.bin/madge ./src --circular" }, "engines": { - "node": ">=12.22.10 <16" + "node": ">=12.22.10 <17" }, "bin": { "parse-server": "bin/parse-server" diff --git a/spec/AudienceRouter.spec.js b/spec/AudienceRouter.spec.js index 208e7834..9f0f7789 100644 --- a/spec/AudienceRouter.spec.js +++ b/spec/AudienceRouter.spec.js @@ -326,22 +326,24 @@ describe('AudiencesRouter', () => { { name: 'My Audience', query: JSON.stringify({ deviceType: 'ios' }) }, { useMasterKey: true } ).then(audience => { - database.collection('test__Audience').updateOne( - { _id: audience.objectId }, - { - $set: { - times_used: 1, - _last_used: now, - }, - }, - {}, - error => { - expect(error).toEqual(null); + database + .collection('test__Audience') + .updateOne( + { _id: audience.objectId }, + { + $set: { + times_used: 1, + _last_used: now, + }, + } + ) + .then(result => { + expect(result).toBeTruthy(); database .collection('test__Audience') .find({ _id: audience.objectId }) .toArray((error, rows) => { - expect(error).toEqual(null); + expect(error).toEqual(undefined); expect(rows[0]['times_used']).toEqual(1); expect(rows[0]['_last_used']).toEqual(now); Parse._request( @@ -361,8 +363,7 @@ describe('AudiencesRouter', () => { done.fail(error); }); }); - } - ); + }); }); }); diff --git a/spec/AuthenticationAdapters.spec.js b/spec/AuthenticationAdapters.spec.js index d32eba04..181ffa04 100644 --- a/spec/AuthenticationAdapters.spec.js +++ b/spec/AuthenticationAdapters.spec.js @@ -1707,6 +1707,26 @@ describe('Apple Game Center Auth adapter', () => { expect(e.message).toBe('Apple Game Center - invalid publicKeyUrl: invalid.com'); } }); + + it('validateAuthData invalid public key http url', async () => { + const authData = { + id: 'G:1965586982', + publicKeyUrl: 'http://static.gc.apple.com/public-key/gc-prod-4.cer', + timestamp: 1565257031287, + signature: '1234', + salt: 'DzqqrQ==', + bundleId: 'cloud.xtralife.gamecenterauth', + }; + + try { + await gcenter.validateAuthData(authData); + fail(); + } catch (e) { + expect(e.message).toBe( + 'Apple Game Center - invalid publicKeyUrl: http://static.gc.apple.com/public-key/gc-prod-4.cer' + ); + } + }); }); describe('phant auth adapter', () => { diff --git a/spec/CloudCode.spec.js b/spec/CloudCode.spec.js index d5e61137..4b8df9f9 100644 --- a/spec/CloudCode.spec.js +++ b/spec/CloudCode.spec.js @@ -1999,6 +1999,25 @@ describe('beforeFind hooks', () => { }); }); + it('should have object found with nested relational data query', async () => { + const obj1 = Parse.Object.extend('TestObject'); + const obj2 = Parse.Object.extend('TestObject2'); + let item2 = new obj2(); + item2 = await item2.save(); + let item1 = new obj1(); + const relation = item1.relation('rel'); + relation.add(item2); + item1 = await item1.save(); + Parse.Cloud.beforeFind('TestObject', req => { + const additionalQ = new Parse.Query('TestObject'); + additionalQ.equalTo('rel', item2); + return Parse.Query.and(req.query, additionalQ); + }); + const q = new Parse.Query('TestObject'); + const res = await q.first(); + expect(res.id).toEqual(item1.id); + }); + it('should use the modified exclude query', async () => { Parse.Cloud.beforeFind('MyObject', req => { const q = req.query; @@ -3205,6 +3224,21 @@ describe('afterLogin hook', () => { const query = new Parse.Query(TestObject); await query.find({ context: { a: 'a' } }); }); + + it('beforeFind and afterFind should have access to context while making fetch call', async () => { + Parse.Cloud.beforeFind('TestObject', req => { + expect(req.context.a).toEqual('a'); + expect(req.context.b).toBeUndefined(); + req.context.b = 'b'; + }); + Parse.Cloud.afterFind('TestObject', req => { + expect(req.context.a).toEqual('a'); + expect(req.context.b).toEqual('b'); + }); + const obj = new TestObject(); + await obj.save(); + await obj.fetch({ context: { a: 'a' } }); + }); }); describe('saveFile hooks', () => { @@ -3516,23 +3550,4 @@ describe('sendEmail', () => { 'Failed to send email because no mail adapter is configured for Parse Server.' ); }); - - it('should have object found with nested relational data query', async () => { - const obj1 = Parse.Object.extend('TestObject'); - const obj2 = Parse.Object.extend('TestObject2'); - let item2 = new obj2(); - item2 = await item2.save(); - let item1 = new obj1(); - const relation = item1.relation('rel'); - relation.add(item2); - item1 = await item1.save(); - Parse.Cloud.beforeFind('TestObject', req => { - const additionalQ = new Parse.Query('TestObject'); - additionalQ.equalTo('rel', item2); - return Parse.Query.and(req.query, additionalQ); - }); - const q = new Parse.Query('TestObject'); - const res = await q.first(); - expect(res.id).toEqual(item1.id); - }); }); diff --git a/spec/FilesController.spec.js b/spec/FilesController.spec.js index 996e4c39..9ea66da6 100644 --- a/spec/FilesController.spec.js +++ b/spec/FilesController.spec.js @@ -3,7 +3,6 @@ const WinstonLoggerAdapter = require('../lib/Adapters/Logger/WinstonLoggerAdapte .WinstonLoggerAdapter; const GridFSBucketAdapter = require('../lib/Adapters/Files/GridFSBucketAdapter') .GridFSBucketAdapter; -const GridStoreAdapter = require('../lib/Adapters/Files/GridStoreAdapter').GridStoreAdapter; const Config = require('../lib/Config'); const FilesController = require('../lib/Controllers/FilesController').default; const databaseURI = 'mongodb://localhost:27017/parse'; @@ -24,8 +23,8 @@ const mockAdapter = { describe('FilesController', () => { it('should properly expand objects', done => { const config = Config.get(Parse.applicationId); - const gridStoreAdapter = new GridFSBucketAdapter('mongodb://localhost:27017/parse'); - const filesController = new FilesController(gridStoreAdapter); + const gridFSAdapter = new GridFSBucketAdapter('mongodb://localhost:27017/parse'); + const filesController = new FilesController(gridFSAdapter); const result = filesController.expandFilesInObject(config, function () {}); expect(result).toBeUndefined(); @@ -88,19 +87,19 @@ describe('FilesController', () => { it('should add a unique hash to the file name when the preserveFileName option is false', done => { const config = Config.get(Parse.applicationId); - const gridStoreAdapter = new GridFSBucketAdapter('mongodb://localhost:27017/parse'); - spyOn(gridStoreAdapter, 'createFile'); - gridStoreAdapter.createFile.and.returnValue(Promise.resolve()); + const gridFSAdapter = new GridFSBucketAdapter('mongodb://localhost:27017/parse'); + spyOn(gridFSAdapter, 'createFile'); + gridFSAdapter.createFile.and.returnValue(Promise.resolve()); const fileName = 'randomFileName.pdf'; const regexEscapedFileName = fileName.replace(/\./g, '\\$&'); - const filesController = new FilesController(gridStoreAdapter, null, { + const filesController = new FilesController(gridFSAdapter, null, { preserveFileName: false, }); filesController.createFile(config, fileName); - expect(gridStoreAdapter.createFile).toHaveBeenCalledTimes(1); - expect(gridStoreAdapter.createFile.calls.mostRecent().args[0]).toMatch( + expect(gridFSAdapter.createFile).toHaveBeenCalledTimes(1); + expect(gridFSAdapter.createFile.calls.mostRecent().args[0]).toMatch( `^.{32}_${regexEscapedFileName}$` ); @@ -109,42 +108,42 @@ describe('FilesController', () => { it('should not add a unique hash to the file name when the preserveFileName option is true', done => { const config = Config.get(Parse.applicationId); - const gridStoreAdapter = new GridFSBucketAdapter('mongodb://localhost:27017/parse'); - spyOn(gridStoreAdapter, 'createFile'); - gridStoreAdapter.createFile.and.returnValue(Promise.resolve()); + const gridFSAdapter = new GridFSBucketAdapter('mongodb://localhost:27017/parse'); + spyOn(gridFSAdapter, 'createFile'); + gridFSAdapter.createFile.and.returnValue(Promise.resolve()); const fileName = 'randomFileName.pdf'; - const filesController = new FilesController(gridStoreAdapter, null, { + const filesController = new FilesController(gridFSAdapter, null, { preserveFileName: true, }); filesController.createFile(config, fileName); - expect(gridStoreAdapter.createFile).toHaveBeenCalledTimes(1); - expect(gridStoreAdapter.createFile.calls.mostRecent().args[0]).toEqual(fileName); + expect(gridFSAdapter.createFile).toHaveBeenCalledTimes(1); + expect(gridFSAdapter.createFile.calls.mostRecent().args[0]).toEqual(fileName); done(); }); it('should handle adapter without getMetadata', async () => { - const gridStoreAdapter = new GridFSBucketAdapter(databaseURI); - gridStoreAdapter.getMetadata = null; - const filesController = new FilesController(gridStoreAdapter); + const gridFSAdapter = new GridFSBucketAdapter(databaseURI); + gridFSAdapter.getMetadata = null; + const filesController = new FilesController(gridFSAdapter); const result = await filesController.getMetadata(); expect(result).toEqual({}); }); it('should reject slashes in file names', done => { - const gridStoreAdapter = new GridFSBucketAdapter('mongodb://localhost:27017/parse'); + const gridFSAdapter = new GridFSBucketAdapter('mongodb://localhost:27017/parse'); const fileName = 'foo/randomFileName.pdf'; - expect(gridStoreAdapter.validateFilename(fileName)).not.toBe(null); + expect(gridFSAdapter.validateFilename(fileName)).not.toBe(null); done(); }); it('should also reject slashes in file names', done => { - const gridStoreAdapter = new GridStoreAdapter('mongodb://localhost:27017/parse'); + const gridFSAdapter = new GridFSBucketAdapter('mongodb://localhost:27017/parse'); const fileName = 'foo/randomFileName.pdf'; - expect(gridStoreAdapter.validateFilename(fileName)).not.toBe(null); + expect(gridFSAdapter.validateFilename(fileName)).not.toBe(null); done(); }); }); diff --git a/spec/GridFSBucketStorageAdapter.spec.js b/spec/GridFSBucketStorageAdapter.spec.js index 8431d6d7..7ffdced2 100644 --- a/spec/GridFSBucketStorageAdapter.spec.js +++ b/spec/GridFSBucketStorageAdapter.spec.js @@ -1,4 +1,3 @@ -const GridStoreAdapter = require('../lib/Adapters/Files/GridStoreAdapter').GridStoreAdapter; const GridFSBucketAdapter = require('../lib/Adapters/Files/GridFSBucketAdapter') .GridFSBucketAdapter; const { randomString } = require('../lib/cryptoUtils'); @@ -14,25 +13,13 @@ async function expectMissingFile(gfsAdapter, name) { } } -describe_only_db('mongo')('GridFSBucket and GridStore interop', () => { +describe_only_db('mongo')('GridFSBucket', () => { beforeEach(async () => { - const gsAdapter = new GridStoreAdapter(databaseURI); + const gsAdapter = new GridFSBucketAdapter(databaseURI); const db = await gsAdapter._connect(); await db.dropDatabase(); }); - it('a file created in GridStore should be available in GridFS', async () => { - const gsAdapter = new GridStoreAdapter(databaseURI); - const gfsAdapter = new GridFSBucketAdapter(databaseURI); - await expectMissingFile(gfsAdapter, 'myFileName'); - const originalString = 'abcdefghi'; - await gsAdapter.createFile('myFileName', originalString); - const gsResult = await gsAdapter.getFileData('myFileName'); - expect(gsResult.toString('utf8')).toBe(originalString); - const gfsResult = await gfsAdapter.getFileData('myFileName'); - expect(gfsResult.toString('utf8')).toBe(originalString); - }); - it('should save an encrypted file that can only be decrypted by a GridFS adapter with the encryptionKey', async () => { const unencryptedAdapter = new GridFSBucketAdapter(databaseURI); const encryptedAdapter = new GridFSBucketAdapter( @@ -451,7 +438,7 @@ describe_only_db('mongo')('GridFSBucket and GridStore interop', () => { await db.admin().serverStatus(); expect(false).toBe(true); } catch (e) { - expect(e.message).toEqual('topology was destroyed'); + expect(e.message).toEqual('MongoClient must be connected to perform this operation'); } }); }); diff --git a/spec/GridStoreAdapter.spec.js b/spec/GridStoreAdapter.spec.js deleted file mode 100644 index 145b8e0e..00000000 --- a/spec/GridStoreAdapter.spec.js +++ /dev/null @@ -1,111 +0,0 @@ -const MongoClient = require('mongodb').MongoClient; -const GridStore = require('mongodb').GridStore; - -const GridStoreAdapter = require('../lib/Adapters/Files/GridStoreAdapter').GridStoreAdapter; -const Config = require('../lib/Config'); -const FilesController = require('../lib/Controllers/FilesController').default; - -// Small additional tests to improve overall coverage -describe_only_db('mongo')('GridStoreAdapter', () => { - it('should properly instanciate the GridStore when deleting a file', async done => { - const databaseURI = 'mongodb://localhost:27017/parse'; - const config = Config.get(Parse.applicationId); - const gridStoreAdapter = new GridStoreAdapter(databaseURI); - const db = await gridStoreAdapter._connect(); - await db.dropDatabase(); - const filesController = new FilesController(gridStoreAdapter, Parse.applicationId, {}); - - // save original unlink before redefinition - const originalUnlink = GridStore.prototype.unlink; - - let gridStoreMode; - - // new unlink method that will capture the mode in which GridStore was opened - GridStore.prototype.unlink = function () { - // restore original unlink during first call - GridStore.prototype.unlink = originalUnlink; - - gridStoreMode = this.mode; - - return originalUnlink.call(this); - }; - - filesController - .createFile(config, 'myFilename.txt', 'my file content', 'text/plain') - .then(myFile => { - return MongoClient.connect(databaseURI) - .then(client => { - const database = client.db(client.s.options.dbName); - // Verify the existance of the fs.files document - return database - .collection('fs.files') - .count() - .then(count => { - expect(count).toEqual(1); - return { database, client }; - }); - }) - .then(({ database, client }) => { - // Verify the existance of the fs.files document - return database - .collection('fs.chunks') - .count() - .then(count => { - expect(count).toEqual(1); - return client.close(); - }); - }) - .then(() => { - return filesController.deleteFile(config, myFile.name); - }); - }) - .then(() => { - return MongoClient.connect(databaseURI) - .then(client => { - const database = client.db(client.s.options.dbName); - // Verify the existance of the fs.files document - return database - .collection('fs.files') - .count() - .then(count => { - expect(count).toEqual(0); - return { database, client }; - }); - }) - .then(({ database, client }) => { - // Verify the existance of the fs.files document - return database - .collection('fs.chunks') - .count() - .then(count => { - expect(count).toEqual(0); - return client.close(); - }); - }); - }) - .then(() => { - // Verify that gridStore was opened in read only mode - expect(gridStoreMode).toEqual('r'); - - done(); - }) - .catch(fail); - }); - - it('handleShutdown, close connection', async () => { - const databaseURI = 'mongodb://localhost:27017/parse'; - const gridStoreAdapter = new GridStoreAdapter(databaseURI); - - const db = await gridStoreAdapter._connect(); - const status = await db.admin().serverStatus(); - expect(status.connections.current > 0).toEqual(true); - - await gridStoreAdapter.handleShutdown(); - try { - await db.admin().serverStatus(); - expect(false).toBe(true); - } catch (e) { - expect(e.message).toEqual('topology was destroyed'); - } - }); -}); diff --git a/spec/Idempotency.spec.js b/spec/Idempotency.spec.js index c2ef8665..813923b1 100644 --- a/spec/Idempotency.spec.js +++ b/spec/Idempotency.spec.js @@ -6,11 +6,14 @@ const rest = require('../lib/rest'); const auth = require('../lib/Auth'); const uuid = require('uuid'); -describe_only_db('mongo')('Idempotency', () => { +describe('Idempotency', () => { // Parameters /** Enable TTL expiration simulated by removing entry instead of waiting for MongoDB TTL monitor which runs only every 60s, so it can take up to 119s until entry removal - ain't nobody got time for that */ const SIMULATE_TTL = true; + const ttl = 2; + const maxTimeOut = 4000; + // Helpers async function deleteRequestEntry(reqId) { const config = Config.get(Parse.applicationId); @@ -38,9 +41,10 @@ describe_only_db('mongo')('Idempotency', () => { } await setup({ paths: ['functions/.*', 'jobs/.*', 'classes/.*', 'users', 'installations'], - ttl: 30, + ttl: ttl, }); }); + // Tests it('should enforce idempotency for cloud code function', async () => { let counter = 0; @@ -56,7 +60,7 @@ describe_only_db('mongo')('Idempotency', () => { 'X-Parse-Request-Id': 'abc-123', }, }; - expect(Config.get(Parse.applicationId).idempotencyOptions.ttl).toBe(30); + expect(Config.get(Parse.applicationId).idempotencyOptions.ttl).toBe(ttl); await request(params); await request(params).then(fail, e => { expect(e.status).toEqual(400); @@ -83,12 +87,38 @@ describe_only_db('mongo')('Idempotency', () => { if (SIMULATE_TTL) { await deleteRequestEntry('abc-123'); } else { - await new Promise(resolve => setTimeout(resolve, 130000)); + await new Promise(resolve => setTimeout(resolve, maxTimeOut)); } await expectAsync(request(params)).toBeResolved(); expect(counter).toBe(2); }); + it_only_db('postgres')( + 'should delete request entry when postgress ttl function is called', + async () => { + const client = Config.get(Parse.applicationId).database.adapter._client; + let counter = 0; + Parse.Cloud.define('myFunction', () => { + counter++; + }); + const params = { + method: 'POST', + url: 'http://localhost:8378/1/functions/myFunction', + headers: { + 'X-Parse-Application-Id': Parse.applicationId, + 'X-Parse-Master-Key': Parse.masterKey, + 'X-Parse-Request-Id': 'abc-123', + }, + }; + await expectAsync(request(params)).toBeResolved(); + await expectAsync(request(params)).toBeRejected(); + await new Promise(resolve => setTimeout(resolve, maxTimeOut)); + await client.one('SELECT idempotency_delete_expired_records()'); + await expectAsync(request(params)).toBeResolved(); + expect(counter).toBe(2); + } + ); + it('should enforce idempotency for cloud code jobs', async () => { let counter = 0; Parse.Cloud.job('myJob', () => { diff --git a/spec/MongoStorageAdapter.spec.js b/spec/MongoStorageAdapter.spec.js index f6d28664..a31a6134 100644 --- a/spec/MongoStorageAdapter.spec.js +++ b/spec/MongoStorageAdapter.spec.js @@ -1,7 +1,7 @@ 'use strict'; const MongoStorageAdapter = require('../lib/Adapters/Storage/Mongo/MongoStorageAdapter').default; -const { MongoClient } = require('mongodb'); +const { MongoClient, Collection } = require('mongodb'); const databaseURI = 'mongodb://localhost:27017/parseServerMongoAdapterTestDatabase'; const request = require('../lib/request'); const Config = require('../lib/Config'); @@ -101,7 +101,7 @@ describe_only_db('mongo')('MongoStorageAdapter', () => { done.fail('Find succeeded despite taking too long!'); }, err => { - expect(err.name).toEqual('MongoError'); + expect(err.name).toEqual('MongoServerError'); expect(err.code).toEqual(50); expect(err.message).toMatch('operation exceeded time limit'); done(); @@ -283,7 +283,7 @@ describe_only_db('mongo')('MongoStorageAdapter', () => { await adapter.database.admin().serverStatus(); expect(false).toBe(true); } catch (e) { - expect(e.message).toEqual('topology was destroyed'); + expect(e.message).toEqual('MongoClient must be connected to perform this operation'); } }); @@ -392,8 +392,7 @@ describe_only_db('mongo')('MongoStorageAdapter', () => { const myObject = new Parse.Object('MyObject'); await myObject.save(); - const databaseAdapter = Config.get(Parse.applicationId).database.adapter; - spyOn(databaseAdapter.database.serverConfig, 'command').and.callThrough(); + spyOn(Collection.prototype, 'findOneAndUpdate').and.callThrough(); await request({ method: 'POST', @@ -412,9 +411,9 @@ describe_only_db('mongo')('MongoStorageAdapter', () => { }); let found = false; - databaseAdapter.database.serverConfig.command.calls.all().forEach(call => { + Collection.prototype.findOneAndUpdate.calls.all().forEach(call => { found = true; - expect(call.args[2].session.transaction.state).not.toBe('NO_TRANSACTION'); + expect(call.args[2].session.transaction.state).toBe('TRANSACTION_COMMITTED'); }); expect(found).toBe(true); }); @@ -423,8 +422,7 @@ describe_only_db('mongo')('MongoStorageAdapter', () => { const myObject = new Parse.Object('MyObject'); await myObject.save(); - const databaseAdapter = Config.get(Parse.applicationId).database.adapter; - spyOn(databaseAdapter.database.serverConfig, 'command').and.callThrough(); + spyOn(Collection.prototype, 'findOneAndUpdate').and.callThrough(); await request({ method: 'POST', @@ -443,9 +441,9 @@ describe_only_db('mongo')('MongoStorageAdapter', () => { }); let found = false; - databaseAdapter.database.serverConfig.command.calls.all().forEach(call => { + Collection.prototype.findOneAndUpdate.calls.all().forEach(call => { found = true; - expect(call.args[2].session).toBe(undefined); + expect(call.args[2].session).toBeFalsy(); }); expect(found).toBe(true); }); @@ -454,8 +452,7 @@ describe_only_db('mongo')('MongoStorageAdapter', () => { const myObject = new Parse.Object('MyObject'); await myObject.save(); - const databaseAdapter = Config.get(Parse.applicationId).database.adapter; - spyOn(databaseAdapter.database.serverConfig, 'command').and.callThrough(); + spyOn(Collection.prototype, 'findOneAndUpdate').and.callThrough(); await request({ method: 'POST', @@ -473,9 +470,9 @@ describe_only_db('mongo')('MongoStorageAdapter', () => { }); let found = false; - databaseAdapter.database.serverConfig.command.calls.all().forEach(call => { + Collection.prototype.findOneAndUpdate.calls.all().forEach(call => { found = true; - expect(call.args[2].session).toBe(undefined); + expect(call.args[2].session).toBeFalsy(); }); expect(found).toBe(true); }); @@ -484,8 +481,7 @@ describe_only_db('mongo')('MongoStorageAdapter', () => { const myObject = new Parse.Object('MyObject'); await myObject.save(); - const databaseAdapter = Config.get(Parse.applicationId).database.adapter; - spyOn(databaseAdapter.database.serverConfig, 'command').and.callThrough(); + spyOn(Collection.prototype, 'findOneAndUpdate').and.callThrough(); await request({ method: 'PUT', @@ -495,30 +491,28 @@ describe_only_db('mongo')('MongoStorageAdapter', () => { }); let found = false; - databaseAdapter.database.serverConfig.command.calls.all().forEach(call => { + Collection.prototype.findOneAndUpdate.calls.all().forEach(call => { found = true; - expect(call.args[2].session).toBe(undefined); + expect(call.args[2].session).toBeFalsy(); }); expect(found).toBe(true); }); it('should not use transactions when using SDK insert', async () => { - const databaseAdapter = Config.get(Parse.applicationId).database.adapter; - spyOn(databaseAdapter.database.serverConfig, 'insert').and.callThrough(); + spyOn(Collection.prototype, 'insertOne').and.callThrough(); const myObject = new Parse.Object('MyObject'); await myObject.save(); - const calls = databaseAdapter.database.serverConfig.insert.calls.all(); + const calls = Collection.prototype.insertOne.calls.all(); expect(calls.length).toBeGreaterThan(0); calls.forEach(call => { - expect(call.args[2].session.transaction.state).toBe('NO_TRANSACTION'); + expect(call.args[1].session).toBeFalsy(); }); }); it('should not use transactions when using SDK update', async () => { - const databaseAdapter = Config.get(Parse.applicationId).database.adapter; - spyOn(databaseAdapter.database.serverConfig, 'update').and.callThrough(); + spyOn(Collection.prototype, 'findOneAndUpdate').and.callThrough(); const myObject = new Parse.Object('MyObject'); await myObject.save(); @@ -526,26 +520,25 @@ describe_only_db('mongo')('MongoStorageAdapter', () => { myObject.set('myAttribute', 'myValue'); await myObject.save(); - const calls = databaseAdapter.database.serverConfig.update.calls.all(); + const calls = Collection.prototype.findOneAndUpdate.calls.all(); expect(calls.length).toBeGreaterThan(0); calls.forEach(call => { - expect(call.args[2].session.transaction.state).toBe('NO_TRANSACTION'); + expect(call.args[2].session).toBeFalsy(); }); }); it('should not use transactions when using SDK delete', async () => { - const databaseAdapter = Config.get(Parse.applicationId).database.adapter; - spyOn(databaseAdapter.database.serverConfig, 'remove').and.callThrough(); + spyOn(Collection.prototype, 'deleteMany').and.callThrough(); const myObject = new Parse.Object('MyObject'); await myObject.save(); await myObject.destroy(); - const calls = databaseAdapter.database.serverConfig.remove.calls.all(); + const calls = Collection.prototype.deleteMany.calls.all(); expect(calls.length).toBeGreaterThan(0); calls.forEach(call => { - expect(call.args[2].session.transaction.state).toBe('NO_TRANSACTION'); + expect(call.args[1].session).toBeFalsy(); }); }); }); diff --git a/spec/MongoTransform.spec.js b/spec/MongoTransform.spec.js index 6834a6b2..60adb4b7 100644 --- a/spec/MongoTransform.spec.js +++ b/spec/MongoTransform.spec.js @@ -4,6 +4,7 @@ const transform = require('../lib/Adapters/Storage/Mongo/MongoTransform'); const dd = require('deep-diff'); const mongodb = require('mongodb'); +const Utils = require('../lib/Utils'); describe('parseObjectToMongoObjectForCreate', () => { it('a basic number', done => { @@ -592,7 +593,7 @@ describe('relativeTimeToDate', () => { describe('In the future', () => { it('should parse valid natural time', () => { const text = 'in 1 year 2 weeks 12 days 10 hours 24 minutes 30 seconds'; - const { result, status, info } = transform.relativeTimeToDate(text, now); + const { result, status, info } = Utils.relativeTimeToDate(text, now); expect(result.toISOString()).toBe('2018-10-22T23:52:46.617Z'); expect(status).toBe('success'); expect(info).toBe('future'); @@ -602,7 +603,7 @@ describe('relativeTimeToDate', () => { describe('In the past', () => { it('should parse valid natural time', () => { const text = '2 days 12 hours 1 minute 12 seconds ago'; - const { result, status, info } = transform.relativeTimeToDate(text, now); + const { result, status, info } = Utils.relativeTimeToDate(text, now); expect(result.toISOString()).toBe('2017-09-24T01:27:04.617Z'); expect(status).toBe('success'); expect(info).toBe('past'); @@ -612,7 +613,7 @@ describe('relativeTimeToDate', () => { describe('From now', () => { it('should equal current time', () => { const text = 'now'; - const { result, status, info } = transform.relativeTimeToDate(text, now); + const { result, status, info } = Utils.relativeTimeToDate(text, now); expect(result.toISOString()).toBe('2017-09-26T13:28:16.617Z'); expect(status).toBe('success'); expect(info).toBe('present'); @@ -621,54 +622,54 @@ describe('relativeTimeToDate', () => { describe('Error cases', () => { it('should error if string is completely gibberish', () => { - expect(transform.relativeTimeToDate('gibberishasdnklasdnjklasndkl123j123')).toEqual({ + expect(Utils.relativeTimeToDate('gibberishasdnklasdnjklasndkl123j123')).toEqual({ status: 'error', info: "Time should either start with 'in' or end with 'ago'", }); }); it('should error if string contains neither `ago` nor `in`', () => { - expect(transform.relativeTimeToDate('12 hours 1 minute')).toEqual({ + expect(Utils.relativeTimeToDate('12 hours 1 minute')).toEqual({ status: 'error', info: "Time should either start with 'in' or end with 'ago'", }); }); it('should error if there are missing units or numbers', () => { - expect(transform.relativeTimeToDate('in 12 hours 1')).toEqual({ + expect(Utils.relativeTimeToDate('in 12 hours 1')).toEqual({ status: 'error', info: 'Invalid time string. Dangling unit or number.', }); - expect(transform.relativeTimeToDate('12 hours minute ago')).toEqual({ + expect(Utils.relativeTimeToDate('12 hours minute ago')).toEqual({ status: 'error', info: 'Invalid time string. Dangling unit or number.', }); }); it('should error on floating point numbers', () => { - expect(transform.relativeTimeToDate('in 12.3 hours')).toEqual({ + expect(Utils.relativeTimeToDate('in 12.3 hours')).toEqual({ status: 'error', info: "'12.3' is not an integer.", }); }); it('should error if numbers are invalid', () => { - expect(transform.relativeTimeToDate('12 hours 123a minute ago')).toEqual({ + expect(Utils.relativeTimeToDate('12 hours 123a minute ago')).toEqual({ status: 'error', info: "'123a' is not an integer.", }); }); it('should error on invalid interval units', () => { - expect(transform.relativeTimeToDate('4 score 7 years ago')).toEqual({ + expect(Utils.relativeTimeToDate('4 score 7 years ago')).toEqual({ status: 'error', info: "Invalid interval: 'score'", }); }); it("should error when string contains 'ago' and 'in'", () => { - expect(transform.relativeTimeToDate('in 1 day 2 minutes ago')).toEqual({ + expect(Utils.relativeTimeToDate('in 1 day 2 minutes ago')).toEqual({ status: 'error', info: "Time cannot have both 'in' and 'ago'", }); diff --git a/spec/ParseGraphQLServer.spec.js b/spec/ParseGraphQLServer.spec.js index c197facb..32a52613 100644 --- a/spec/ParseGraphQLServer.spec.js +++ b/spec/ParseGraphQLServer.spec.js @@ -1,7 +1,7 @@ const http = require('http'); const express = require('express'); const req = require('../lib/request'); -const fetch = require('node-fetch'); +const fetch = (...args) => import('node-fetch').then(({ default: fetch }) => fetch(...args)); const FormData = require('form-data'); const ws = require('ws'); require('./helper'); @@ -9,13 +9,16 @@ const { updateCLP } = require('./support/dev'); const pluralize = require('pluralize'); const { getMainDefinition } = require('apollo-utilities'); -const { ApolloLink, split } = require('apollo-link'); -const { createHttpLink } = require('apollo-link-http'); -const { InMemoryCache } = require('apollo-cache-inmemory'); const { createUploadLink } = require('apollo-upload-client'); const { SubscriptionClient } = require('subscriptions-transport-ws'); -const { WebSocketLink } = require('apollo-link-ws'); -const ApolloClient = require('apollo-client').default; +const { WebSocketLink } = require('@apollo/client/link/ws'); +const { + ApolloClient, + InMemoryCache, + ApolloLink, + split, + createHttpLink, +} = require('@apollo/client/core'); const gql = require('graphql-tag'); const { toGlobalId } = require('graphql-relay'); const { @@ -29,7 +32,7 @@ const { } = require('graphql'); const { ParseServer } = require('../'); const { ParseGraphQLServer } = require('../lib/GraphQL/ParseGraphQLServer'); -const ReadPreference = require('mongodb').ReadPreference; +const { ReadPreference, Collection } = require('mongodb'); const { v4: uuidv4 } = require('uuid'); function handleError(e) { @@ -2597,11 +2600,23 @@ describe('ParseGraphQLServer', () => { // "SecondaryObject:bBRgmzIRRM" < "SecondaryObject:nTMcuVbATY" true // base64("SecondaryObject:bBRgmzIRRM"") < base64(""SecondaryObject:nTMcuVbATY"") false // "U2Vjb25kYXJ5T2JqZWN0OmJCUmdteklSUk0=" < "U2Vjb25kYXJ5T2JqZWN0Om5UTWN1VmJBVFk=" false + const originalIds = [ + getSecondaryObjectsResult.data.secondaryObject2.objectId, + getSecondaryObjectsResult.data.secondaryObject4.objectId, + ]; expect( findSecondaryObjectsResult.data.secondaryObjects.edges[0].node.objectId - ).toBeLessThan( - findSecondaryObjectsResult.data.secondaryObjects.edges[1].node.objectId - ); + ).not.toBe(findSecondaryObjectsResult.data.secondaryObjects.edges[1].node.objectId); + expect( + originalIds.includes( + findSecondaryObjectsResult.data.secondaryObjects.edges[0].node.objectId + ) + ).toBeTrue(); + expect( + originalIds.includes( + findSecondaryObjectsResult.data.secondaryObjects.edges[1].node.objectId + ) + ).toBeTrue(); const createPrimaryObjectResult = await apolloClient.mutate({ mutation: gql` @@ -4458,8 +4473,7 @@ describe('ParseGraphQLServer', () => { await parseGraphQLServer.parseGraphQLSchema.schemaCache.clear(); - const databaseAdapter = parseServer.config.databaseController.adapter; - spyOn(databaseAdapter.database.serverConfig, 'cursor').and.callThrough(); + spyOn(Collection.prototype, 'find').and.callThrough(); await apolloClient.query({ query: gql` @@ -4483,13 +4497,13 @@ describe('ParseGraphQLServer', () => { let foundGraphQLClassReadPreference = false; let foundUserClassReadPreference = false; - databaseAdapter.database.serverConfig.cursor.calls.all().forEach(call => { - if (call.args[0].ns.collection.indexOf('GraphQLClass') >= 0) { + Collection.prototype.find.calls.all().forEach(call => { + if (call.object.s.namespace.collection.indexOf('GraphQLClass') >= 0) { foundGraphQLClassReadPreference = true; - expect(call.args[0].options.readPreference.mode).toBe(ReadPreference.PRIMARY); - } else if (call.args[0].ns.collection.indexOf('_User') >= 0) { + expect(call.object.s.readPreference.mode).toBe(ReadPreference.PRIMARY); + } else if (call.object.s.namespace.collection.indexOf('_User') >= 0) { foundUserClassReadPreference = true; - expect(call.args[0].options.readPreference.mode).toBe(ReadPreference.PRIMARY); + expect(call.object.s.readPreference.mode).toBe(ReadPreference.PRIMARY); } }); @@ -4505,8 +4519,7 @@ describe('ParseGraphQLServer', () => { await parseGraphQLServer.parseGraphQLSchema.schemaCache.clear(); - const databaseAdapter = parseServer.config.databaseController.adapter; - spyOn(databaseAdapter.database.serverConfig, 'cursor').and.callThrough(); + spyOn(Collection.prototype, 'find').and.callThrough(); await apolloClient.query({ query: gql` @@ -4530,13 +4543,13 @@ describe('ParseGraphQLServer', () => { let foundGraphQLClassReadPreference = false; let foundUserClassReadPreference = false; - databaseAdapter.database.serverConfig.cursor.calls.all().forEach(call => { - if (call.args[0].ns.collection.indexOf('GraphQLClass') >= 0) { + Collection.prototype.find.calls.all().forEach(call => { + if (call.object.s.namespace.collection.indexOf('GraphQLClass') >= 0) { foundGraphQLClassReadPreference = true; - expect(call.args[0].options.readPreference.mode).toBe(ReadPreference.SECONDARY); - } else if (call.args[0].ns.collection.indexOf('_User') >= 0) { + expect(call.args[1].readPreference).toBe(ReadPreference.SECONDARY); + } else if (call.object.s.namespace.collection.indexOf('_User') >= 0) { foundUserClassReadPreference = true; - expect(call.args[0].options.readPreference.mode).toBe(ReadPreference.SECONDARY); + expect(call.args[1].readPreference).toBe(ReadPreference.SECONDARY); } }); @@ -4549,8 +4562,7 @@ describe('ParseGraphQLServer', () => { await parseGraphQLServer.parseGraphQLSchema.schemaCache.clear(); - const databaseAdapter = parseServer.config.databaseController.adapter; - spyOn(databaseAdapter.database.serverConfig, 'cursor').and.callThrough(); + spyOn(Collection.prototype, 'find').and.callThrough(); await apolloClient.query({ query: gql` @@ -4577,13 +4589,13 @@ describe('ParseGraphQLServer', () => { let foundGraphQLClassReadPreference = false; let foundUserClassReadPreference = false; - databaseAdapter.database.serverConfig.cursor.calls.all().forEach(call => { - if (call.args[0].ns.collection.indexOf('GraphQLClass') >= 0) { + Collection.prototype.find.calls.all().forEach(call => { + if (call.object.s.namespace.collection.indexOf('GraphQLClass') >= 0) { foundGraphQLClassReadPreference = true; - expect(call.args[0].options.readPreference.mode).toBe(ReadPreference.SECONDARY); - } else if (call.args[0].ns.collection.indexOf('_User') >= 0) { + expect(call.args[1].readPreference).toBe(ReadPreference.SECONDARY); + } else if (call.object.s.namespace.collection.indexOf('_User') >= 0) { foundUserClassReadPreference = true; - expect(call.args[0].options.readPreference.mode).toBe(ReadPreference.NEAREST); + expect(call.args[1].readPreference).toBe(ReadPreference.NEAREST); } }); @@ -5441,8 +5453,7 @@ describe('ParseGraphQLServer', () => { await parseGraphQLServer.parseGraphQLSchema.schemaCache.clear(); - const databaseAdapter = parseServer.config.databaseController.adapter; - spyOn(databaseAdapter.database.serverConfig, 'cursor').and.callThrough(); + spyOn(Collection.prototype, 'find').and.callThrough(); await apolloClient.query({ query: gql` @@ -5467,13 +5478,13 @@ describe('ParseGraphQLServer', () => { let foundGraphQLClassReadPreference = false; let foundUserClassReadPreference = false; - databaseAdapter.database.serverConfig.cursor.calls.all().forEach(call => { - if (call.args[0].ns.collection.indexOf('GraphQLClass') >= 0) { + Collection.prototype.find.calls.all().forEach(call => { + if (call.object.s.namespace.collection.indexOf('GraphQLClass') >= 0) { foundGraphQLClassReadPreference = true; - expect(call.args[0].options.readPreference.mode).toBe(ReadPreference.PRIMARY); - } else if (call.args[0].ns.collection.indexOf('_User') >= 0) { + expect(call.object.s.readPreference.mode).toBe(ReadPreference.PRIMARY); + } else if (call.object.s.namespace.collection.indexOf('_User') >= 0) { foundUserClassReadPreference = true; - expect(call.args[0].options.readPreference.mode).toBe(ReadPreference.PRIMARY); + expect(call.object.s.readPreference.mode).toBe(ReadPreference.PRIMARY); } }); @@ -5486,8 +5497,7 @@ describe('ParseGraphQLServer', () => { await parseGraphQLServer.parseGraphQLSchema.schemaCache.clear(); - const databaseAdapter = parseServer.config.databaseController.adapter; - spyOn(databaseAdapter.database.serverConfig, 'cursor').and.callThrough(); + spyOn(Collection.prototype, 'find').and.callThrough(); await apolloClient.query({ query: gql` @@ -5512,13 +5522,13 @@ describe('ParseGraphQLServer', () => { let foundGraphQLClassReadPreference = false; let foundUserClassReadPreference = false; - databaseAdapter.database.serverConfig.cursor.calls.all().forEach(call => { - if (call.args[0].ns.collection.indexOf('GraphQLClass') >= 0) { + Collection.prototype.find.calls.all().forEach(call => { + if (call.object.s.namespace.collection.indexOf('GraphQLClass') >= 0) { foundGraphQLClassReadPreference = true; - expect(call.args[0].options.readPreference.mode).toBe(ReadPreference.SECONDARY); - } else if (call.args[0].ns.collection.indexOf('_User') >= 0) { + expect(call.args[1].readPreference).toBe(ReadPreference.SECONDARY); + } else if (call.object.s.namespace.collection.indexOf('_User') >= 0) { foundUserClassReadPreference = true; - expect(call.args[0].options.readPreference.mode).toBe(ReadPreference.SECONDARY); + expect(call.args[1].readPreference).toBe(ReadPreference.SECONDARY); } }); @@ -5531,8 +5541,7 @@ describe('ParseGraphQLServer', () => { await parseGraphQLServer.parseGraphQLSchema.schemaCache.clear(); - const databaseAdapter = parseServer.config.databaseController.adapter; - spyOn(databaseAdapter.database.serverConfig, 'cursor').and.callThrough(); + spyOn(Collection.prototype, 'find').and.callThrough(); await apolloClient.query({ query: gql` @@ -5559,13 +5568,13 @@ describe('ParseGraphQLServer', () => { let foundGraphQLClassReadPreference = false; let foundUserClassReadPreference = false; - databaseAdapter.database.serverConfig.cursor.calls.all().forEach(call => { - if (call.args[0].ns.collection.indexOf('GraphQLClass') >= 0) { + Collection.prototype.find.calls.all().forEach(call => { + if (call.object.s.namespace.collection.indexOf('GraphQLClass') >= 0) { foundGraphQLClassReadPreference = true; - expect(call.args[0].options.readPreference.mode).toBe(ReadPreference.SECONDARY); - } else if (call.args[0].ns.collection.indexOf('_User') >= 0) { + expect(call.args[1].readPreference).toBe(ReadPreference.SECONDARY); + } else if (call.object.s.namespace.collection.indexOf('_User') >= 0) { foundUserClassReadPreference = true; - expect(call.args[0].options.readPreference.mode).toBe(ReadPreference.NEAREST); + expect(call.args[1].readPreference).toBe(ReadPreference.NEAREST); } }); @@ -5579,8 +5588,7 @@ describe('ParseGraphQLServer', () => { await parseGraphQLServer.parseGraphQLSchema.schemaCache.clear(); - const databaseAdapter = parseServer.config.databaseController.adapter; - spyOn(databaseAdapter.database.serverConfig, 'cursor').and.callThrough(); + spyOn(Collection.prototype, 'find').and.callThrough(); await apolloClient.query({ query: gql` @@ -5617,13 +5625,13 @@ describe('ParseGraphQLServer', () => { let foundGraphQLClassReadPreference = false; let foundUserClassReadPreference = false; - databaseAdapter.database.serverConfig.cursor.calls.all().forEach(call => { - if (call.args[0].ns.collection.indexOf('GraphQLClass') >= 0) { + Collection.prototype.find.calls.all().forEach(call => { + if (call.object.s.namespace.collection.indexOf('GraphQLClass') >= 0) { foundGraphQLClassReadPreference = true; - expect(call.args[0].options.readPreference.mode).toBe(ReadPreference.SECONDARY); - } else if (call.args[0].ns.collection.indexOf('_User') >= 0) { + expect(call.args[1].readPreference).toBe(ReadPreference.SECONDARY); + } else if (call.object.s.namespace.collection.indexOf('_User') >= 0) { foundUserClassReadPreference = true; - expect(call.args[0].options.readPreference.mode).toBe(ReadPreference.NEAREST); + expect(call.args[1].readPreference).toBe(ReadPreference.NEAREST); } }); diff --git a/spec/ParseQuery.spec.js b/spec/ParseQuery.spec.js index bf870c92..0dcc8363 100644 --- a/spec/ParseQuery.spec.js +++ b/spec/ParseQuery.spec.js @@ -4766,7 +4766,7 @@ describe('Parse.Query testing', () => { .catch(done.fail); }); - it_only_db('mongo')('should handle relative times correctly', function (done) { + it('should handle relative times correctly', async () => { const now = Date.now(); const obj1 = new Parse.Object('MyCustomObject', { name: 'obj1', @@ -4777,94 +4777,75 @@ describe('Parse.Query testing', () => { ttl: new Date(now - 2 * 24 * 60 * 60 * 1000), // 2 days ago }); - Parse.Object.saveAll([obj1, obj2]) - .then(() => { - const q = new Parse.Query('MyCustomObject'); - q.greaterThan('ttl', { $relativeTime: 'in 1 day' }); - return q.find({ useMasterKey: true }); - }) - .then(results => { - expect(results.length).toBe(1); - }) - .then(() => { - const q = new Parse.Query('MyCustomObject'); - q.greaterThan('ttl', { $relativeTime: '1 day ago' }); - return q.find({ useMasterKey: true }); - }) - .then(results => { - expect(results.length).toBe(1); - }) - .then(() => { - const q = new Parse.Query('MyCustomObject'); - q.lessThan('ttl', { $relativeTime: '5 days ago' }); - return q.find({ useMasterKey: true }); - }) - .then(results => { - expect(results.length).toBe(0); - }) - .then(() => { - const q = new Parse.Query('MyCustomObject'); - q.greaterThan('ttl', { $relativeTime: '3 days ago' }); - return q.find({ useMasterKey: true }); - }) - .then(results => { - expect(results.length).toBe(2); - }) - .then(() => { - const q = new Parse.Query('MyCustomObject'); - q.greaterThan('ttl', { $relativeTime: 'now' }); - return q.find({ useMasterKey: true }); - }) - .then(results => { - expect(results.length).toBe(1); - }) - .then(() => { - const q = new Parse.Query('MyCustomObject'); - q.greaterThan('ttl', { $relativeTime: 'now' }); - q.lessThan('ttl', { $relativeTime: 'in 1 day' }); - return q.find({ useMasterKey: true }); - }) - .then(results => { - expect(results.length).toBe(0); - }) - .then(() => { - const q = new Parse.Query('MyCustomObject'); - q.greaterThan('ttl', { $relativeTime: '1 year 3 weeks ago' }); - return q.find({ useMasterKey: true }); - }) - .then(results => { - expect(results.length).toBe(2); - }) - .then(done, done.fail); + await Parse.Object.saveAll([obj1, obj2]); + const q1 = new Parse.Query('MyCustomObject'); + q1.greaterThan('ttl', { $relativeTime: 'in 1 day' }); + const results1 = await q1.find({ useMasterKey: true }); + expect(results1.length).toBe(1); + + const q2 = new Parse.Query('MyCustomObject'); + q2.greaterThan('ttl', { $relativeTime: '1 day ago' }); + const results2 = await q2.find({ useMasterKey: true }); + expect(results2.length).toBe(1); + + const q3 = new Parse.Query('MyCustomObject'); + q3.lessThan('ttl', { $relativeTime: '5 days ago' }); + const results3 = await q3.find({ useMasterKey: true }); + expect(results3.length).toBe(0); + + const q4 = new Parse.Query('MyCustomObject'); + q4.greaterThan('ttl', { $relativeTime: '3 days ago' }); + const results4 = await q4.find({ useMasterKey: true }); + expect(results4.length).toBe(2); + + const q5 = new Parse.Query('MyCustomObject'); + q5.greaterThan('ttl', { $relativeTime: 'now' }); + const results5 = await q5.find({ useMasterKey: true }); + expect(results5.length).toBe(1); + + const q6 = new Parse.Query('MyCustomObject'); + q6.greaterThan('ttl', { $relativeTime: 'now' }); + q6.lessThan('ttl', { $relativeTime: 'in 1 day' }); + const results6 = await q6.find({ useMasterKey: true }); + expect(results6.length).toBe(0); + + const q7 = new Parse.Query('MyCustomObject'); + q7.greaterThan('ttl', { $relativeTime: '1 year 3 weeks ago' }); + const results7 = await q7.find({ useMasterKey: true }); + expect(results7.length).toBe(2); }); - it_only_db('mongo')('should error on invalid relative time', function (done) { + it('should error on invalid relative time', async () => { const obj1 = new Parse.Object('MyCustomObject', { name: 'obj1', ttl: new Date(Date.now() + 2 * 24 * 60 * 60 * 1000), // 2 days from now }); - + await obj1.save({ useMasterKey: true }); const q = new Parse.Query('MyCustomObject'); q.greaterThan('ttl', { $relativeTime: '-12 bananas ago' }); - obj1 - .save({ useMasterKey: true }) - .then(() => q.find({ useMasterKey: true })) - .then(done.fail, () => done()); + try { + await q.find({ useMasterKey: true }); + fail('Should have thrown error'); + } catch (error) { + expect(error.code).toBe(Parse.Error.INVALID_JSON); + } }); - it_only_db('mongo')('should error when using $relativeTime on non-Date field', function (done) { + it('should error when using $relativeTime on non-Date field', async () => { const obj1 = new Parse.Object('MyCustomObject', { name: 'obj1', nonDateField: 'abcd', ttl: new Date(Date.now() + 2 * 24 * 60 * 60 * 1000), // 2 days from now }); - + await obj1.save({ useMasterKey: true }); const q = new Parse.Query('MyCustomObject'); q.greaterThan('nonDateField', { $relativeTime: '1 day ago' }); - obj1 - .save({ useMasterKey: true }) - .then(() => q.find({ useMasterKey: true })) - .then(done.fail, () => done()); + try { + await q.find({ useMasterKey: true }); + fail('Should have thrown error'); + } catch (error) { + expect(error.code).toBe(Parse.Error.INVALID_JSON); + } }); it('should match complex structure with dot notation when using matchesKeyInQuery', function (done) { diff --git a/spec/ParseServerRESTController.spec.js b/spec/ParseServerRESTController.spec.js index 90fe3832..0ec52ed4 100644 --- a/spec/ParseServerRESTController.spec.js +++ b/spec/ParseServerRESTController.spec.js @@ -15,35 +15,18 @@ describe('ParseServerRESTController', () => { ); }); - it('should handle a get request', done => { - RESTController.request('GET', '/classes/MyObject').then( - res => { - expect(res.results.length).toBe(0); - done(); - }, - err => { - console.log(err); - jfail(err); - done(); - } - ); + it('should handle a get request', async () => { + const res = await RESTController.request('GET', '/classes/MyObject'); + expect(res.results.length).toBe(0); }); - it('should handle a get request with full serverURL mount path', done => { - RESTController.request('GET', '/1/classes/MyObject').then( - res => { - expect(res.results.length).toBe(0); - done(); - }, - err => { - jfail(err); - done(); - } - ); + it('should handle a get request with full serverURL mount path', async () => { + const res = await RESTController.request('GET', '/1/classes/MyObject'); + expect(res.results.length).toBe(0); }); - it('should handle a POST batch without transaction', done => { - RESTController.request('POST', 'batch', { + it('should handle a POST batch without transaction', async () => { + const res = await RESTController.request('POST', 'batch', { requests: [ { method: 'GET', @@ -59,20 +42,12 @@ describe('ParseServerRESTController', () => { path: '/classes/MyObject', }, ], - }).then( - res => { - expect(res.length).toBe(3); - done(); - }, - err => { - jfail(err); - done(); - } - ); + }); + expect(res.length).toBe(3); }); - it('should handle a POST batch with transaction=false', done => { - RESTController.request('POST', 'batch', { + it('should handle a POST batch with transaction=false', async () => { + const res = await RESTController.request('POST', 'batch', { requests: [ { method: 'GET', @@ -89,16 +64,8 @@ describe('ParseServerRESTController', () => { }, ], transaction: false, - }).then( - res => { - expect(res.length).toBe(3); - done(); - }, - err => { - jfail(err); - done(); - } - ); + }); + expect(res.length).toBe(3); }); it('should handle response status', async () => { @@ -186,54 +153,40 @@ describe('ParseServerRESTController', () => { } }); - it('should handle a batch request with transaction = true', async done => { - await reconfigureServer(); + it('should handle a batch request with transaction = true', async () => { const myObject = new Parse.Object('MyObject'); // This is important because transaction only works on pre-existing collections - myObject - .save() - .then(() => { - return myObject.destroy(); - }) - .then(() => { - spyOn(databaseAdapter, 'createObject').and.callThrough(); - - return RESTController.request('POST', 'batch', { - requests: [ - { - method: 'POST', - path: '/1/classes/MyObject', - body: { key: 'value1' }, - }, - { - method: 'POST', - path: '/1/classes/MyObject', - body: { key: 'value2' }, - }, - ], - transaction: true, - }).then(response => { - expect(response.length).toEqual(2); - expect(response[0].success.objectId).toBeDefined(); - expect(response[0].success.createdAt).toBeDefined(); - expect(response[1].success.objectId).toBeDefined(); - expect(response[1].success.createdAt).toBeDefined(); - const query = new Parse.Query('MyObject'); - return query.find().then(results => { - expect(databaseAdapter.createObject.calls.count() % 2).toBe(0); - for (let i = 0; i + 1 < databaseAdapter.createObject.calls.length; i = i + 2) { - expect(databaseAdapter.createObject.calls.argsFor(i)[3]).toBe( - databaseAdapter.createObject.calls.argsFor(i + 1)[3] - ); - } - expect(results.map(result => result.get('key')).sort()).toEqual([ - 'value1', - 'value2', - ]); - done(); - }); - }); - }) - .catch(done.fail); + await myObject.save(); + await myObject.destroy(); + spyOn(databaseAdapter, 'createObject').and.callThrough(); + const response = await RESTController.request('POST', 'batch', { + requests: [ + { + method: 'POST', + path: '/1/classes/MyObject', + body: { key: 'value1' }, + }, + { + method: 'POST', + path: '/1/classes/MyObject', + body: { key: 'value2' }, + }, + ], + transaction: true, + }); + expect(response.length).toEqual(2); + expect(response[0].success.objectId).toBeDefined(); + expect(response[0].success.createdAt).toBeDefined(); + expect(response[1].success.objectId).toBeDefined(); + expect(response[1].success.createdAt).toBeDefined(); + const query = new Parse.Query('MyObject'); + const results = await query.find(); + expect(databaseAdapter.createObject.calls.count() % 2).toBe(0); + for (let i = 0; i + 1 < databaseAdapter.createObject.calls.length; i = i + 2) { + expect(databaseAdapter.createObject.calls.argsFor(i)[3]).toBe( + databaseAdapter.createObject.calls.argsFor(i + 1)[3] + ); + } + expect(results.map(result => result.get('key')).sort()).toEqual(['value1', 'value2']); }); it('should not save anything when one operation fails in a transaction', async () => { @@ -560,21 +513,11 @@ describe('ParseServerRESTController', () => { }); } - it('should handle a POST request', done => { - RESTController.request('POST', '/classes/MyObject', { key: 'value' }) - .then(() => { - return RESTController.request('GET', '/classes/MyObject'); - }) - .then(res => { - expect(res.results.length).toBe(1); - expect(res.results[0].key).toEqual('value'); - done(); - }) - .catch(err => { - console.log(err); - jfail(err); - done(); - }); + it('should handle a POST request', async () => { + await RESTController.request('POST', '/classes/MyObject', { key: 'value' }); + const res = await RESTController.request('GET', '/classes/MyObject'); + expect(res.results.length).toBe(1); + expect(res.results[0].key).toEqual('value'); }); it('should handle a POST request with context', async () => { @@ -593,125 +536,76 @@ describe('ParseServerRESTController', () => { ); }); - it('ensures sessionTokens are properly handled', done => { - let userId; - Parse.User.signUp('user', 'pass') - .then(user => { - userId = user.id; - const sessionToken = user.getSessionToken(); - return RESTController.request('GET', '/users/me', undefined, { - sessionToken, - }); - }) - .then(res => { - // Result is in JSON format - expect(res.objectId).toEqual(userId); - done(); - }) - .catch(err => { - console.log(err); - jfail(err); - done(); + it('ensures sessionTokens are properly handled', async () => { + const user = await Parse.User.signUp('user', 'pass'); + const sessionToken = user.getSessionToken(); + const res = await RESTController.request('GET', '/users/me', undefined, { + sessionToken, + }); + // Result is in JSON format + expect(res.objectId).toEqual(user.id); + }); + + it('ensures masterKey is properly handled', async () => { + const user = await Parse.User.signUp('user', 'pass'); + const userId = user.id; + await Parse.User.logOut(); + const res = await RESTController.request('GET', '/classes/_User', undefined, { + useMasterKey: true, + }); + expect(res.results.length).toBe(1); + expect(res.results[0].objectId).toEqual(userId); + }); + + it('ensures no user is created when passing an empty username', async () => { + try { + await RESTController.request('POST', '/classes/_User', { + username: '', + password: 'world', }); + fail('Success callback should not be called when passing an empty username.'); + } catch (err) { + expect(err.code).toBe(Parse.Error.USERNAME_MISSING); + expect(err.message).toBe('bad or missing username'); + } }); - it('ensures masterKey is properly handled', done => { - let userId; - Parse.User.signUp('user', 'pass') - .then(user => { - userId = user.id; - return Parse.User.logOut().then(() => { - return RESTController.request('GET', '/classes/_User', undefined, { - useMasterKey: true, - }); - }); - }) - .then( - res => { - expect(res.results.length).toBe(1); - expect(res.results[0].objectId).toEqual(userId); - done(); - }, - err => { - jfail(err); - done(); - } - ); + it('ensures no user is created when passing an empty password', async () => { + try { + await RESTController.request('POST', '/classes/_User', { + username: 'hello', + password: '', + }); + fail('Success callback should not be called when passing an empty password.'); + } catch (err) { + expect(err.code).toBe(Parse.Error.PASSWORD_MISSING); + expect(err.message).toBe('password is required'); + } }); - it('ensures no user is created when passing an empty username', done => { - RESTController.request('POST', '/classes/_User', { - username: '', - password: 'world', - }).then( - () => { - jfail(new Error('Success callback should not be called when passing an empty username.')); - done(); - }, - err => { - expect(err.code).toBe(Parse.Error.USERNAME_MISSING); - expect(err.message).toBe('bad or missing username'); - done(); - } - ); - }); - - it('ensures no user is created when passing an empty password', done => { - RESTController.request('POST', '/classes/_User', { - username: 'hello', - password: '', - }).then( - () => { - jfail(new Error('Success callback should not be called when passing an empty password.')); - done(); - }, - err => { - expect(err.code).toBe(Parse.Error.PASSWORD_MISSING); - expect(err.message).toBe('password is required'); - done(); - } - ); - }); - - it('ensures no session token is created on creating users', done => { - RESTController.request('POST', '/classes/_User', { + it('ensures no session token is created on creating users', async () => { + const user = await RESTController.request('POST', '/classes/_User', { username: 'hello', password: 'world', - }) - .then(user => { - expect(user.sessionToken).toBeUndefined(); - const query = new Parse.Query('_Session'); - return query.find({ useMasterKey: true }); - }) - .then(sessions => { - expect(sessions.length).toBe(0); - done(); - }, done.fail); + }); + expect(user.sessionToken).toBeUndefined(); + const query = new Parse.Query('_Session'); + const sessions = await query.find({ useMasterKey: true }); + expect(sessions.length).toBe(0); }); - it('ensures a session token is created when passing installationId != cloud', done => { - RESTController.request( + it('ensures a session token is created when passing installationId != cloud', async () => { + const user = await RESTController.request( 'POST', '/classes/_User', { username: 'hello', password: 'world' }, { installationId: 'my-installation' } - ) - .then(user => { - expect(user.sessionToken).not.toBeUndefined(); - const query = new Parse.Query('_Session'); - return query.find({ useMasterKey: true }); - }) - .then( - sessions => { - expect(sessions.length).toBe(1); - expect(sessions[0].get('installationId')).toBe('my-installation'); - done(); - }, - err => { - jfail(err); - done(); - } - ); + ); + expect(user.sessionToken).not.toBeUndefined(); + const query = new Parse.Query('_Session'); + const sessions = await query.find({ useMasterKey: true }); + expect(sessions.length).toBe(1); + expect(sessions[0].get('installationId')).toBe('my-installation'); }); it('ensures logIn is saved with installationId', async () => { diff --git a/spec/PostgresInitOptions.spec.js b/spec/PostgresInitOptions.spec.js index 069d5a64..1e3282ad 100644 --- a/spec/PostgresInitOptions.spec.js +++ b/spec/PostgresInitOptions.spec.js @@ -4,8 +4,7 @@ const PostgresStorageAdapter = require('../lib/Adapters/Storage/Postgres/Postgre const postgresURI = process.env.PARSE_SERVER_TEST_DATABASE_URI || 'postgres://localhost:5432/parse_server_postgres_adapter_test_database'; -const ParseServer = require('../lib/index'); -const express = require('express'); + //public schema const databaseOptions1 = { initOptions: { @@ -24,72 +23,56 @@ const GameScore = Parse.Object.extend({ className: 'GameScore', }); -function createParseServer(options) { - return new Promise((resolve, reject) => { - const parseServer = new ParseServer.default( - Object.assign({}, defaultConfiguration, options, { - serverURL: 'http://localhost:12668/parse', - serverStartComplete: error => { - if (error) { - reject(error); - } else { - expect(Parse.applicationId).toEqual('test'); - const app = express(); - app.use('/parse', parseServer.app); - - const server = app.listen(12668); - Parse.serverURL = 'http://localhost:12668/parse'; - resolve(server); - } - }, - }) - ); - }); -} - describe_only_db('postgres')('Postgres database init options', () => { - let server; - - afterAll(done => { - if (server) { - Parse.serverURL = 'http://localhost:8378/1'; - server.close(done); - } - }); - - it('should create server with public schema databaseOptions', done => { + it('should create server with public schema databaseOptions', async () => { const adapter = new PostgresStorageAdapter({ uri: postgresURI, collectionPrefix: 'test_', databaseOptions: databaseOptions1, }); - - createParseServer({ databaseAdapter: adapter }) - .then(newServer => { - server = newServer; - const score = new GameScore({ - score: 1337, - playerName: 'Sean Plott', - cheatMode: false, - }); - return score.save(); - }) - .then(async () => { - await reconfigureServer(); - done(); - }, done.fail); + await reconfigureServer({ + databaseAdapter: adapter, + }); + const score = new GameScore({ + score: 1337, + playerName: 'Sean Plott', + cheatMode: false, + }); + await score.save(); }); - it('should fail to create server if schema databaseOptions does not exist', done => { + it('should create server using postgresql uri with public schema databaseOptions', async () => { + const postgresURI2 = new URL(postgresURI); + postgresURI2.protocol = 'postgresql:'; + const adapter = new PostgresStorageAdapter({ + uri: postgresURI2.toString(), + collectionPrefix: 'test_', + databaseOptions: databaseOptions1, + }); + await reconfigureServer({ + databaseAdapter: adapter, + }); + const score = new GameScore({ + score: 1337, + playerName: 'Sean Plott', + cheatMode: false, + }); + await score.save(); + }); + + it('should fail to create server if schema databaseOptions does not exist', async () => { const adapter = new PostgresStorageAdapter({ uri: postgresURI, collectionPrefix: 'test_', databaseOptions: databaseOptions2, }); - - createParseServer({ databaseAdapter: adapter }).then(done.fail, async () => { - await reconfigureServer(); - done(); - }); + try { + await reconfigureServer({ + databaseAdapter: adapter, + }); + fail('Should have thrown error'); + } catch (error) { + expect(error).toBeDefined(); + } }); }); diff --git a/spec/PostgresStorageAdapter.spec.js b/spec/PostgresStorageAdapter.spec.js index b042206d..a43ceb66 100644 --- a/spec/PostgresStorageAdapter.spec.js +++ b/spec/PostgresStorageAdapter.spec.js @@ -149,6 +149,135 @@ describe_only_db('postgres')('PostgresStorageAdapter', () => { await expectAsync(adapter.getClass('UnknownClass')).toBeRejectedWith(undefined); }); + it('$relativeTime should error on $eq', async () => { + const tableName = '_User'; + const schema = { + fields: { + objectId: { type: 'String' }, + username: { type: 'String' }, + email: { type: 'String' }, + emailVerified: { type: 'Boolean' }, + createdAt: { type: 'Date' }, + updatedAt: { type: 'Date' }, + authData: { type: 'Object' }, + }, + }; + const client = adapter._client; + await adapter.createTable(tableName, schema); + await client.none('INSERT INTO $1:name ($2:name, $3:name) VALUES ($4, $5)', [ + tableName, + 'objectId', + 'username', + 'Bugs', + 'Bunny', + ]); + const database = Config.get(Parse.applicationId).database; + await database.loadSchema({ clearCache: true }); + try { + await database.find( + tableName, + { + createdAt: { + $eq: { + $relativeTime: '12 days ago', + }, + }, + }, + {} + ); + fail('Should have thrown error'); + } catch (error) { + expect(error.code).toBe(Parse.Error.INVALID_JSON); + } + await dropTable(client, tableName); + }); + + it('$relativeTime should error on $ne', async () => { + const tableName = '_User'; + const schema = { + fields: { + objectId: { type: 'String' }, + username: { type: 'String' }, + email: { type: 'String' }, + emailVerified: { type: 'Boolean' }, + createdAt: { type: 'Date' }, + updatedAt: { type: 'Date' }, + authData: { type: 'Object' }, + }, + }; + const client = adapter._client; + await adapter.createTable(tableName, schema); + await client.none('INSERT INTO $1:name ($2:name, $3:name) VALUES ($4, $5)', [ + tableName, + 'objectId', + 'username', + 'Bugs', + 'Bunny', + ]); + const database = Config.get(Parse.applicationId).database; + await database.loadSchema({ clearCache: true }); + try { + await database.find( + tableName, + { + createdAt: { + $ne: { + $relativeTime: '12 days ago', + }, + }, + }, + {} + ); + fail('Should have thrown error'); + } catch (error) { + expect(error.code).toBe(Parse.Error.INVALID_JSON); + } + await dropTable(client, tableName); + }); + + it('$relativeTime should error on $exists', async () => { + const tableName = '_User'; + const schema = { + fields: { + objectId: { type: 'String' }, + username: { type: 'String' }, + email: { type: 'String' }, + emailVerified: { type: 'Boolean' }, + createdAt: { type: 'Date' }, + updatedAt: { type: 'Date' }, + authData: { type: 'Object' }, + }, + }; + const client = adapter._client; + await adapter.createTable(tableName, schema); + await client.none('INSERT INTO $1:name ($2:name, $3:name) VALUES ($4, $5)', [ + tableName, + 'objectId', + 'username', + 'Bugs', + 'Bunny', + ]); + const database = Config.get(Parse.applicationId).database; + await database.loadSchema({ clearCache: true }); + try { + await database.find( + tableName, + { + createdAt: { + $exists: { + $relativeTime: '12 days ago', + }, + }, + }, + {} + ); + fail('Should have thrown error'); + } catch (error) { + expect(error.code).toBe(Parse.Error.INVALID_JSON); + } + await dropTable(client, tableName); + }); + it('should use index for caseInsensitive query using Postgres', async () => { const tableName = '_User'; const schema = { @@ -426,9 +555,21 @@ describe_only_db('postgres')('PostgresStorageAdapter', () => { }, classLevelPermissions: undefined, }); - await new Promise(resolve => setTimeout(resolve, 500)); + await new Promise(resolve => setTimeout(resolve, 2000)); expect(adapter._onchange).toHaveBeenCalled(); }); + + it('Idempotency class should have function', async () => { + await reconfigureServer(); + const adapter = Config.get('test').database.adapter; + const client = adapter._client; + const qs = + "SELECT format('%I.%I(%s)', ns.nspname, p.proname, oidvectortypes(p.proargtypes)) FROM pg_proc p INNER JOIN pg_namespace ns ON (p.pronamespace = ns.oid) WHERE p.proname = 'idempotency_delete_expired_records'"; + const foundFunction = await client.one(qs); + expect(foundFunction.format).toBe('public.idempotency_delete_expired_records()'); + await adapter.deleteIdempotencyFunction(); + await client.none(qs); + }); }); describe_only_db('postgres')('PostgresStorageAdapter shutdown', () => { @@ -438,4 +579,13 @@ describe_only_db('postgres')('PostgresStorageAdapter shutdown', () => { adapter.handleShutdown(); expect(adapter._client.$pool.ending).toEqual(true); }); + + it('handleShutdown, close connection of postgresql uri', () => { + const databaseURI2 = new URL(databaseURI); + databaseURI2.protocol = 'postgresql:'; + const adapter = new PostgresStorageAdapter({ uri: databaseURI2.toString() }); + expect(adapter._client.$pool.ending).toEqual(false); + adapter.handleShutdown(); + expect(adapter._client.$pool.ending).toEqual(true); + }); }); diff --git a/spec/PushController.spec.js b/spec/PushController.spec.js index 4626b5e0..49613214 100644 --- a/spec/PushController.spec.js +++ b/spec/PushController.spec.js @@ -562,11 +562,7 @@ describe('PushController', () => { }); const pushStatusId = await sendPush(payload, {}, config, auth); // it is enqueued so it can take time - await new Promise(resolve => { - setTimeout(() => { - resolve(); - }, 1000); - }); + await sleep(1000); Parse.serverURL = 'http://localhost:8378/1'; // GOOD url const result = await Parse.Push.getPushStatus(pushStatusId); expect(result).toBeDefined(); @@ -767,7 +763,7 @@ describe('PushController', () => { }); }); - it('should not schedule push when not configured', done => { + it('should not schedule push when not configured', async () => { const config = Config.get(Parse.applicationId); const auth = { isMaster: true, @@ -800,33 +796,20 @@ describe('PushController', () => { installations.push(installation); } - reconfigureServer({ + await reconfigureServer({ push: { adapter: pushAdapter }, - }) - .then(() => { - return Parse.Object.saveAll(installations) - .then(() => { - return pushController.sendPush(payload, {}, config, auth); - }) - .then(() => new Promise(resolve => setTimeout(resolve, 300))); - }) - .then(() => { - const query = new Parse.Query('_PushStatus'); - return query.find({ useMasterKey: true }).then(results => { - expect(results.length).toBe(1); - const pushStatus = results[0]; - expect(pushStatus.get('status')).not.toBe('scheduled'); - done(); - }); - }) - .catch(err => { - console.error(err); - fail('should not fail'); - done(); - }); + }); + await Parse.Object.saveAll(installations); + await pushController.sendPush(payload, {}, config, auth); + await sleep(1000); + const query = new Parse.Query('_PushStatus'); + const results = await query.find({ useMasterKey: true }); + expect(results.length).toBe(1); + const pushStatus = results[0]; + expect(pushStatus.get('status')).not.toBe('scheduled'); }); - it('should schedule push when configured', done => { + it('should schedule push when configured', async () => { const auth = { isMaster: true, }; @@ -866,28 +849,19 @@ describe('PushController', () => { installation.set('deviceType', 'ios'); installations.push(installation); } - reconfigureServer({ + await reconfigureServer({ push: { adapter: pushAdapter }, scheduledPush: true, - }) - .then(() => { - const config = Config.get(Parse.applicationId); - return Parse.Object.saveAll(installations) - .then(() => { - return pushController.sendPush(payload, {}, config, auth); - }) - .then(() => new Promise(resolve => setTimeout(resolve, 300))); - }) - .then(() => { - const query = new Parse.Query('_PushStatus'); - return query.find({ useMasterKey: true }).then(results => { - expect(results.length).toBe(1); - const pushStatus = results[0]; - expect(pushStatus.get('status')).toBe('scheduled'); - }); - }) - .then(done) - .catch(done.err); + }); + const config = Config.get(Parse.applicationId); + await Parse.Object.saveAll(installations); + await pushController.sendPush(payload, {}, config, auth); + await sleep(1000); + const query = new Parse.Query('_PushStatus'); + const results = await query.find({ useMasterKey: true }); + expect(results.length).toBe(1); + const pushStatus = results[0]; + expect(pushStatus.get('status')).toBe('scheduled'); }); it('should not enqueue push when device token is not set', async () => { diff --git a/spec/ReadPreferenceOption.spec.js b/spec/ReadPreferenceOption.spec.js index f2bc328d..535c35c8 100644 --- a/spec/ReadPreferenceOption.spec.js +++ b/spec/ReadPreferenceOption.spec.js @@ -1,9 +1,8 @@ 'use strict'; const Parse = require('parse/node'); -const ReadPreference = require('mongodb').ReadPreference; +const { ReadPreference, Collection } = require('mongodb'); const request = require('../lib/request'); -const Config = require('../lib/Config'); function waitForReplication() { return new Promise(function (resolve) { @@ -13,8 +12,6 @@ function waitForReplication() { describe_only_db('mongo')('Read preference option', () => { it('should find in primary by default', done => { - const databaseAdapter = Config.get(Parse.applicationId).database.adapter; - const obj0 = new Parse.Object('MyObject'); obj0.set('boolKey', false); const obj1 = new Parse.Object('MyObject'); @@ -22,7 +19,7 @@ describe_only_db('mongo')('Read preference option', () => { Parse.Object.saveAll([obj0, obj1]) .then(() => { - spyOn(databaseAdapter.database.serverConfig, 'cursor').and.callThrough(); + spyOn(Collection.prototype, 'find').and.callThrough(); const query = new Parse.Query('MyObject'); query.equalTo('boolKey', false); @@ -31,10 +28,10 @@ describe_only_db('mongo')('Read preference option', () => { expect(results.length).toBe(1); expect(results[0].get('boolKey')).toBe(false); let myObjectReadPreference = null; - databaseAdapter.database.serverConfig.cursor.calls.all().forEach(call => { - if (call.args[0].ns.collection.indexOf('MyObject') >= 0) { + Collection.prototype.find.calls.all().forEach(call => { + if (call.object.s.namespace.collection.indexOf('MyObject') >= 0) { myObjectReadPreference = true; - expect(call.args[0].options.readPreference.mode).toBe(ReadPreference.PRIMARY); + expect(call.object.s.readPreference.mode).toBe(ReadPreference.PRIMARY); } }); @@ -58,15 +55,13 @@ describe_only_db('mongo')('Read preference option', () => { databaseAdapter: new MongoStorageAdapter(adapterOptions), }); - const databaseAdapter = Config.get(Parse.applicationId).database.adapter; - const obj0 = new Parse.Object('MyObject'); obj0.set('boolKey', false); const obj1 = new Parse.Object('MyObject'); obj1.set('boolKey', true); await Parse.Object.saveAll([obj0, obj1]); - spyOn(databaseAdapter.database.serverConfig, 'cursor').and.callThrough(); + spyOn(Collection.prototype, 'find').and.callThrough(); const query = new Parse.Query('MyObject'); query.equalTo('boolKey', false); @@ -76,10 +71,10 @@ describe_only_db('mongo')('Read preference option', () => { expect(results[0].get('boolKey')).toBe(false); let myObjectReadPreference = null; - databaseAdapter.database.serverConfig.cursor.calls.all().forEach(call => { - if (call.args[0].ns.collection.indexOf('MyObject') >= 0) { + Collection.prototype.find.calls.all().forEach(call => { + if (call.object.s.namespace.collection.indexOf('MyObject') >= 0) { myObjectReadPreference = true; - expect(call.args[0].options.readPreference.mode).toBe(ReadPreference.NEAREST); + expect(call.args[1].readPreference).toBe(ReadPreference.NEAREST); } }); @@ -87,15 +82,13 @@ describe_only_db('mongo')('Read preference option', () => { }); it('should change read preference in the beforeFind trigger', async () => { - const databaseAdapter = Config.get(Parse.applicationId).database.adapter; - const obj0 = new Parse.Object('MyObject'); obj0.set('boolKey', false); const obj1 = new Parse.Object('MyObject'); obj1.set('boolKey', true); await Parse.Object.saveAll([obj0, obj1]); - spyOn(databaseAdapter.database.serverConfig, 'cursor').and.callThrough(); + spyOn(Collection.prototype, 'find').and.callThrough(); Parse.Cloud.beforeFind('MyObject', req => { req.readPreference = 'SECONDARY'; @@ -110,9 +103,9 @@ describe_only_db('mongo')('Read preference option', () => { expect(results[0].get('boolKey')).toBe(false); let myObjectReadPreference = null; - databaseAdapter.database.serverConfig.cursor.calls.all().forEach(call => { - if (call.args[0].ns.collection.indexOf('MyObject') >= 0) { - myObjectReadPreference = call.args[0].options.readPreference.mode; + Collection.prototype.find.calls.all().forEach(call => { + if (call.object.s.namespace.collection.indexOf('MyObject') >= 0) { + myObjectReadPreference = call.args[1].readPreference; } }); @@ -120,15 +113,13 @@ describe_only_db('mongo')('Read preference option', () => { }); it('should check read preference as case insensitive', async () => { - const databaseAdapter = Config.get(Parse.applicationId).database.adapter; - const obj0 = new Parse.Object('MyObject'); obj0.set('boolKey', false); const obj1 = new Parse.Object('MyObject'); obj1.set('boolKey', true); await Parse.Object.saveAll([obj0, obj1]); - spyOn(databaseAdapter.database.serverConfig, 'cursor').and.callThrough(); + spyOn(Collection.prototype, 'find').and.callThrough(); Parse.Cloud.beforeFind('MyObject', req => { req.readPreference = 'sEcOnDarY'; @@ -144,9 +135,9 @@ describe_only_db('mongo')('Read preference option', () => { expect(results[0].get('boolKey')).toBe(false); let myObjectReadPreference = null; - databaseAdapter.database.serverConfig.cursor.calls.all().forEach(call => { - if (call.args[0].ns.collection.indexOf('MyObject') >= 0) { - myObjectReadPreference = call.args[0].options.readPreference.mode; + Collection.prototype.find.calls.all().forEach(call => { + if (call.object.s.namespace.collection.indexOf('MyObject') >= 0) { + myObjectReadPreference = call.args[1].readPreference; } }); @@ -154,15 +145,13 @@ describe_only_db('mongo')('Read preference option', () => { }); it('should change read preference in the beforeFind trigger even changing query', async () => { - const databaseAdapter = Config.get(Parse.applicationId).database.adapter; - const obj0 = new Parse.Object('MyObject'); obj0.set('boolKey', false); const obj1 = new Parse.Object('MyObject'); obj1.set('boolKey', true); await Parse.Object.saveAll([obj0, obj1]); - spyOn(databaseAdapter.database.serverConfig, 'cursor').and.callThrough(); + spyOn(Collection.prototype, 'find').and.callThrough(); Parse.Cloud.beforeFind('MyObject', req => { req.query.equalTo('boolKey', true); @@ -178,9 +167,9 @@ describe_only_db('mongo')('Read preference option', () => { expect(results[0].get('boolKey')).toBe(true); let myObjectReadPreference = null; - databaseAdapter.database.serverConfig.cursor.calls.all().forEach(call => { - if (call.args[0].ns.collection.indexOf('MyObject') >= 0) { - myObjectReadPreference = call.args[0].options.readPreference.mode; + Collection.prototype.find.calls.all().forEach(call => { + if (call.object.s.namespace.collection.indexOf('MyObject') >= 0) { + myObjectReadPreference = call.args[1].readPreference; } }); @@ -188,15 +177,13 @@ describe_only_db('mongo')('Read preference option', () => { }); it('should change read preference in the beforeFind trigger even returning query', async () => { - const databaseAdapter = Config.get(Parse.applicationId).database.adapter; - const obj0 = new Parse.Object('MyObject'); obj0.set('boolKey', false); const obj1 = new Parse.Object('MyObject'); obj1.set('boolKey', true); await Parse.Object.saveAll([obj0, obj1]); - spyOn(databaseAdapter.database.serverConfig, 'cursor').and.callThrough(); + spyOn(Collection.prototype, 'find').and.callThrough(); Parse.Cloud.beforeFind('MyObject', req => { req.readPreference = 'SECONDARY'; @@ -216,9 +203,9 @@ describe_only_db('mongo')('Read preference option', () => { expect(results[0].get('boolKey')).toBe(true); let myObjectReadPreference = null; - databaseAdapter.database.serverConfig.cursor.calls.all().forEach(call => { - if (call.args[0].ns.collection.indexOf('MyObject') >= 0) { - myObjectReadPreference = call.args[0].options.readPreference.mode; + Collection.prototype.find.calls.all().forEach(call => { + if (call.object.s.namespace.collection.indexOf('MyObject') >= 0) { + myObjectReadPreference = call.args[1].readPreference; } }); @@ -226,15 +213,13 @@ describe_only_db('mongo')('Read preference option', () => { }); it('should change read preference in the beforeFind trigger even returning promise', async () => { - const databaseAdapter = Config.get(Parse.applicationId).database.adapter; - const obj0 = new Parse.Object('MyObject'); obj0.set('boolKey', false); const obj1 = new Parse.Object('MyObject'); obj1.set('boolKey', true); await Parse.Object.saveAll([obj0, obj1]); - spyOn(databaseAdapter.database.serverConfig, 'cursor').and.callThrough(); + spyOn(Collection.prototype, 'find').and.callThrough(); Parse.Cloud.beforeFind('MyObject', req => { req.readPreference = 'SECONDARY'; @@ -253,9 +238,9 @@ describe_only_db('mongo')('Read preference option', () => { expect(results[0].get('boolKey')).toBe(true); let myObjectReadPreference = null; - databaseAdapter.database.serverConfig.cursor.calls.all().forEach(call => { - if (call.args[0].ns.collection.indexOf('MyObject') >= 0) { - myObjectReadPreference = call.args[0].options.readPreference.mode; + Collection.prototype.find.calls.all().forEach(call => { + if (call.object.s.namespace.collection.indexOf('MyObject') >= 0) { + myObjectReadPreference = call.args[1].readPreference; } }); @@ -263,15 +248,13 @@ describe_only_db('mongo')('Read preference option', () => { }); it('should change read preference to PRIMARY_PREFERRED', async () => { - const databaseAdapter = Config.get(Parse.applicationId).database.adapter; - const obj0 = new Parse.Object('MyObject'); obj0.set('boolKey', false); const obj1 = new Parse.Object('MyObject'); obj1.set('boolKey', true); await Parse.Object.saveAll([obj0, obj1]); - spyOn(databaseAdapter.database.serverConfig, 'cursor').and.callThrough(); + spyOn(Collection.prototype, 'find').and.callThrough(); Parse.Cloud.beforeFind('MyObject', req => { req.readPreference = 'PRIMARY_PREFERRED'; @@ -286,9 +269,9 @@ describe_only_db('mongo')('Read preference option', () => { expect(results[0].get('boolKey')).toBe(false); let myObjectReadPreference = null; - databaseAdapter.database.serverConfig.cursor.calls.all().forEach(call => { - if (call.args[0].ns.collection.indexOf('MyObject') >= 0) { - myObjectReadPreference = call.args[0].options.readPreference.mode; + Collection.prototype.find.calls.all().forEach(call => { + if (call.object.s.namespace.collection.indexOf('MyObject') >= 0) { + myObjectReadPreference = call.args[1].readPreference; } }); @@ -296,15 +279,13 @@ describe_only_db('mongo')('Read preference option', () => { }); it('should change read preference to SECONDARY_PREFERRED', async () => { - const databaseAdapter = Config.get(Parse.applicationId).database.adapter; - const obj0 = new Parse.Object('MyObject'); obj0.set('boolKey', false); const obj1 = new Parse.Object('MyObject'); obj1.set('boolKey', true); await Parse.Object.saveAll([obj0, obj1]); - spyOn(databaseAdapter.database.serverConfig, 'cursor').and.callThrough(); + spyOn(Collection.prototype, 'find').and.callThrough(); Parse.Cloud.beforeFind('MyObject', req => { req.readPreference = 'SECONDARY_PREFERRED'; @@ -319,9 +300,9 @@ describe_only_db('mongo')('Read preference option', () => { expect(results[0].get('boolKey')).toBe(false); let myObjectReadPreference = null; - databaseAdapter.database.serverConfig.cursor.calls.all().forEach(call => { - if (call.args[0].ns.collection.indexOf('MyObject') >= 0) { - myObjectReadPreference = call.args[0].options.readPreference.mode; + Collection.prototype.find.calls.all().forEach(call => { + if (call.object.s.namespace.collection.indexOf('MyObject') >= 0) { + myObjectReadPreference = call.args[1].readPreference; } }); @@ -329,15 +310,13 @@ describe_only_db('mongo')('Read preference option', () => { }); it('should change read preference to NEAREST', async () => { - const databaseAdapter = Config.get(Parse.applicationId).database.adapter; - const obj0 = new Parse.Object('MyObject'); obj0.set('boolKey', false); const obj1 = new Parse.Object('MyObject'); obj1.set('boolKey', true); await Parse.Object.saveAll([obj0, obj1]); - spyOn(databaseAdapter.database.serverConfig, 'cursor').and.callThrough(); + spyOn(Collection.prototype, 'find').and.callThrough(); Parse.Cloud.beforeFind('MyObject', req => { req.readPreference = 'NEAREST'; @@ -352,9 +331,9 @@ describe_only_db('mongo')('Read preference option', () => { expect(results[0].get('boolKey')).toBe(false); let myObjectReadPreference = null; - databaseAdapter.database.serverConfig.cursor.calls.all().forEach(call => { - if (call.args[0].ns.collection.indexOf('MyObject') >= 0) { - myObjectReadPreference = call.args[0].options.readPreference.mode; + Collection.prototype.find.calls.all().forEach(call => { + if (call.object.s.namespace.collection.indexOf('MyObject') >= 0) { + myObjectReadPreference = call.args[1].readPreference; } }); @@ -362,15 +341,13 @@ describe_only_db('mongo')('Read preference option', () => { }); it('should change read preference for GET', async () => { - const databaseAdapter = Config.get(Parse.applicationId).database.adapter; - const obj0 = new Parse.Object('MyObject'); obj0.set('boolKey', false); const obj1 = new Parse.Object('MyObject'); obj1.set('boolKey', true); await Parse.Object.saveAll([obj0, obj1]); - spyOn(databaseAdapter.database.serverConfig, 'cursor').and.callThrough(); + spyOn(Collection.prototype, 'find').and.callThrough(); Parse.Cloud.beforeFind('MyObject', req => { req.readPreference = 'SECONDARY'; @@ -383,9 +360,9 @@ describe_only_db('mongo')('Read preference option', () => { expect(result.get('boolKey')).toBe(false); let myObjectReadPreference = null; - databaseAdapter.database.serverConfig.cursor.calls.all().forEach(call => { - if (call.args[0].ns.collection.indexOf('MyObject') >= 0) { - myObjectReadPreference = call.args[0].options.readPreference.mode; + Collection.prototype.find.calls.all().forEach(call => { + if (call.object.s.namespace.collection.indexOf('MyObject') >= 0) { + myObjectReadPreference = call.args[1].readPreference; } }); @@ -393,15 +370,13 @@ describe_only_db('mongo')('Read preference option', () => { }); it('should change read preference for GET using API', async () => { - const databaseAdapter = Config.get(Parse.applicationId).database.adapter; - const obj0 = new Parse.Object('MyObject'); obj0.set('boolKey', false); const obj1 = new Parse.Object('MyObject'); obj1.set('boolKey', true); await Parse.Object.saveAll([obj0, obj1]); - spyOn(databaseAdapter.database.serverConfig, 'cursor').and.callThrough(); + spyOn(Collection.prototype, 'find').and.callThrough(); Parse.Cloud.beforeFind('MyObject', req => { req.readPreference = 'SECONDARY'; @@ -421,9 +396,9 @@ describe_only_db('mongo')('Read preference option', () => { expect(body.boolKey).toBe(false); let myObjectReadPreference = null; - databaseAdapter.database.serverConfig.cursor.calls.all().forEach(call => { - if (call.args[0].ns.collection.indexOf('MyObject') >= 0) { - myObjectReadPreference = call.args[0].options.readPreference.mode; + Collection.prototype.find.calls.all().forEach(call => { + if (call.object.s.namespace.collection.indexOf('MyObject') >= 0) { + myObjectReadPreference = call.args[1].readPreference; } }); @@ -431,15 +406,13 @@ describe_only_db('mongo')('Read preference option', () => { }); it('should change read preference for GET directly from API', async () => { - const databaseAdapter = Config.get(Parse.applicationId).database.adapter; - const obj0 = new Parse.Object('MyObject'); obj0.set('boolKey', false); const obj1 = new Parse.Object('MyObject'); obj1.set('boolKey', true); await Parse.Object.saveAll([obj0, obj1]); - spyOn(databaseAdapter.database.serverConfig, 'cursor').and.callThrough(); + spyOn(Collection.prototype, 'find').and.callThrough(); await waitForReplication(); const response = await request({ @@ -454,9 +427,9 @@ describe_only_db('mongo')('Read preference option', () => { expect(response.data.boolKey).toBe(false); let myObjectReadPreference = null; - databaseAdapter.database.serverConfig.cursor.calls.all().forEach(call => { - if (call.args[0].ns.collection.indexOf('MyObject') >= 0) { - myObjectReadPreference = call.args[0].options.readPreference.mode; + Collection.prototype.find.calls.all().forEach(call => { + if (call.object.s.namespace.collection.indexOf('MyObject') >= 0) { + myObjectReadPreference = call.args[1].readPreference; } }); @@ -464,15 +437,13 @@ describe_only_db('mongo')('Read preference option', () => { }); it('should change read preference for GET using API through the beforeFind overriding API option', async () => { - const databaseAdapter = Config.get(Parse.applicationId).database.adapter; - const obj0 = new Parse.Object('MyObject'); obj0.set('boolKey', false); const obj1 = new Parse.Object('MyObject'); obj1.set('boolKey', true); await Parse.Object.saveAll([obj0, obj1]); - spyOn(databaseAdapter.database.serverConfig, 'cursor').and.callThrough(); + spyOn(Collection.prototype, 'find').and.callThrough(); Parse.Cloud.beforeFind('MyObject', req => { req.readPreference = 'SECONDARY_PREFERRED'; @@ -491,9 +462,9 @@ describe_only_db('mongo')('Read preference option', () => { expect(response.data.boolKey).toBe(false); let myObjectReadPreference = null; - databaseAdapter.database.serverConfig.cursor.calls.all().forEach(call => { - if (call.args[0].ns.collection.indexOf('MyObject') >= 0) { - myObjectReadPreference = call.args[0].options.readPreference.mode; + Collection.prototype.find.calls.all().forEach(call => { + if (call.object.s.namespace.collection.indexOf('MyObject') >= 0) { + myObjectReadPreference = call.args[1].readPreference; } }); @@ -501,15 +472,13 @@ describe_only_db('mongo')('Read preference option', () => { }); it('should change read preference for FIND using API through beforeFind trigger', async () => { - const databaseAdapter = Config.get(Parse.applicationId).database.adapter; - const obj0 = new Parse.Object('MyObject'); obj0.set('boolKey', false); const obj1 = new Parse.Object('MyObject'); obj1.set('boolKey', true); await Parse.Object.saveAll([obj0, obj1]); - spyOn(databaseAdapter.database.serverConfig, 'cursor').and.callThrough(); + spyOn(Collection.prototype, 'find').and.callThrough(); Parse.Cloud.beforeFind('MyObject', req => { req.readPreference = 'SECONDARY'; @@ -528,9 +497,9 @@ describe_only_db('mongo')('Read preference option', () => { expect(response.data.results.length).toEqual(2); let myObjectReadPreference = null; - databaseAdapter.database.serverConfig.cursor.calls.all().forEach(call => { - if (call.args[0].ns.collection.indexOf('MyObject') >= 0) { - myObjectReadPreference = call.args[0].options.readPreference.mode; + Collection.prototype.find.calls.all().forEach(call => { + if (call.object.s.namespace.collection.indexOf('MyObject') >= 0) { + myObjectReadPreference = call.args[1].readPreference; } }); @@ -538,15 +507,13 @@ describe_only_db('mongo')('Read preference option', () => { }); it('should change read preference for FIND directly from API', async () => { - const databaseAdapter = Config.get(Parse.applicationId).database.adapter; - const obj0 = new Parse.Object('MyObject'); obj0.set('boolKey', false); const obj1 = new Parse.Object('MyObject'); obj1.set('boolKey', true); await Parse.Object.saveAll([obj0, obj1]); - spyOn(databaseAdapter.database.serverConfig, 'cursor').and.callThrough(); + spyOn(Collection.prototype, 'find').and.callThrough(); await waitForReplication(); const response = await request({ @@ -561,9 +528,9 @@ describe_only_db('mongo')('Read preference option', () => { expect(response.data.results.length).toEqual(2); let myObjectReadPreference = null; - databaseAdapter.database.serverConfig.cursor.calls.all().forEach(call => { - if (call.args[0].ns.collection.indexOf('MyObject') >= 0) { - myObjectReadPreference = call.args[0].options.readPreference.mode; + Collection.prototype.find.calls.all().forEach(call => { + if (call.object.s.namespace.collection.indexOf('MyObject') >= 0) { + myObjectReadPreference = call.args[1].readPreference; } }); @@ -571,15 +538,13 @@ describe_only_db('mongo')('Read preference option', () => { }); it('should change read preference for FIND using API through the beforeFind overriding API option', async () => { - const databaseAdapter = Config.get(Parse.applicationId).database.adapter; - const obj0 = new Parse.Object('MyObject'); obj0.set('boolKey', false); const obj1 = new Parse.Object('MyObject'); obj1.set('boolKey', true); await Parse.Object.saveAll([obj0, obj1]); - spyOn(databaseAdapter.database.serverConfig, 'cursor').and.callThrough(); + spyOn(Collection.prototype, 'find').and.callThrough(); Parse.Cloud.beforeFind('MyObject', req => { req.readPreference = 'SECONDARY_PREFERRED'; @@ -598,9 +563,9 @@ describe_only_db('mongo')('Read preference option', () => { expect(response.data.results.length).toEqual(2); let myObjectReadPreference = null; - databaseAdapter.database.serverConfig.cursor.calls.all().forEach(call => { - if (call.args[0].ns.collection.indexOf('MyObject') >= 0) { - myObjectReadPreference = call.args[0].options.readPreference.mode; + Collection.prototype.find.calls.all().forEach(call => { + if (call.object.s.namespace.collection.indexOf('MyObject') >= 0) { + myObjectReadPreference = call.args[1].readPreference; } }); @@ -608,15 +573,13 @@ describe_only_db('mongo')('Read preference option', () => { }); xit('should change read preference for count', done => { - const databaseAdapter = Config.get(Parse.applicationId).database.adapter; - const obj0 = new Parse.Object('MyObject'); obj0.set('boolKey', false); const obj1 = new Parse.Object('MyObject'); obj1.set('boolKey', true); Parse.Object.saveAll([obj0, obj1]).then(() => { - spyOn(databaseAdapter.database.serverConfig, 'cursor').and.callThrough(); + spyOn(Collection.prototype, 'find').and.callThrough(); Parse.Cloud.beforeFind('MyObject', req => { req.readPreference = 'SECONDARY'; @@ -631,9 +594,9 @@ describe_only_db('mongo')('Read preference option', () => { expect(result).toBe(1); let myObjectReadPreference = null; - databaseAdapter.database.serverConfig.cursor.calls.all().forEach(call => { - if (call.args[0].ns.collection.indexOf('MyObject') >= 0) { - myObjectReadPreference = call.args[0].options.readPreference.mode; + Collection.prototype.find.calls.all().forEach(call => { + if (call.object.s.namespace.collection.indexOf('MyObject') >= 0) { + myObjectReadPreference = call.args[1].readPreference; } }); @@ -659,17 +622,16 @@ describe_only_db('mongo')('Read preference option', () => { await waitForReplication(); // Spy on DB adapter - const databaseAdapter = Config.get(Parse.applicationId).database.adapter; - spyOn(databaseAdapter.database.serverConfig, 'startSession').and.callThrough(); + spyOn(Collection.prototype, 'aggregate').and.callThrough(); // Query const query = new Parse.Query('MyObject'); const results = await query.aggregate([{ match: { boolKey: false } }]); // Validate expect(results.length).toBe(1); let readPreference = null; - databaseAdapter.database.serverConfig.startSession.calls.all().forEach(call => { - if (call.args[0].owner.ns.indexOf('MyObject') > -1) { - readPreference = call.args[0].owner.operation.readPreference.mode; + Collection.prototype.aggregate.calls.all().forEach(call => { + if (call.object.s.namespace.collection.indexOf('MyObject') > -1) { + readPreference = call.args[1].readPreference; } }); expect(readPreference).toEqual(ReadPreference.SECONDARY); @@ -685,8 +647,7 @@ describe_only_db('mongo')('Read preference option', () => { await waitForReplication(); // Spy on DB adapter - const databaseAdapter = Config.get(Parse.applicationId).database.adapter; - spyOn(databaseAdapter.database.serverConfig, 'cursor').and.callThrough(); + spyOn(Collection.prototype, 'find').and.callThrough(); // Query const query = new Parse.Query('MyObject'); query.equalTo('boolKey', false); @@ -695,9 +656,9 @@ describe_only_db('mongo')('Read preference option', () => { // Validate expect(results.length).toBe(1); let myObjectReadPreference = null; - databaseAdapter.database.serverConfig.cursor.calls.all().forEach(call => { - if (call.args[0].ns.collection.indexOf('MyObject') >= 0) { - myObjectReadPreference = call.args[0].options.readPreference.mode; + Collection.prototype.find.calls.all().forEach(call => { + if (call.object.s.namespace.collection.indexOf('MyObject') >= 0) { + myObjectReadPreference = call.args[1].readPreference; } }); expect(myObjectReadPreference).toEqual(ReadPreference.SECONDARY); @@ -713,8 +674,7 @@ describe_only_db('mongo')('Read preference option', () => { await waitForReplication(); // Spy on DB adapter - const databaseAdapter = Config.get(Parse.applicationId).database.adapter; - spyOn(databaseAdapter.database.serverConfig, 'startSession').and.callThrough(); + spyOn(Collection.prototype, 'aggregate').and.callThrough(); // Query const query = new Parse.Query('MyObject'); query.readPreference('SECONDARY'); @@ -722,17 +682,15 @@ describe_only_db('mongo')('Read preference option', () => { // Validate expect(results.length).toBe(1); let readPreference = null; - databaseAdapter.database.serverConfig.startSession.calls.all().forEach(call => { - if (call.args[0].owner.ns.indexOf('MyObject') > -1) { - readPreference = call.args[0].owner.operation.readPreference.mode; + Collection.prototype.aggregate.calls.all().forEach(call => { + if (call.object.s.namespace.collection.indexOf('MyObject') > -1) { + readPreference = call.args[1].readPreference; } }); expect(readPreference).toEqual(ReadPreference.SECONDARY); }); it('should find includes in same replica of readPreference by default', async () => { - const databaseAdapter = Config.get(Parse.applicationId).database.adapter; - const obj0 = new Parse.Object('MyObject0'); obj0.set('boolKey', false); const obj1 = new Parse.Object('MyObject1'); @@ -743,7 +701,7 @@ describe_only_db('mongo')('Read preference option', () => { obj2.set('myObject1', obj1); await Parse.Object.saveAll([obj0, obj1, obj2]); - spyOn(databaseAdapter.database.serverConfig, 'cursor').and.callThrough(); + spyOn(Collection.prototype, 'find').and.callThrough(); Parse.Cloud.beforeFind('MyObject2', req => { req.readPreference = 'SECONDARY'; @@ -765,15 +723,15 @@ describe_only_db('mongo')('Read preference option', () => { let myObjectReadPreference0 = null; let myObjectReadPreference1 = null; let myObjectReadPreference2 = null; - databaseAdapter.database.serverConfig.cursor.calls.all().forEach(call => { - if (call.args[0].ns.collection.indexOf('MyObject0') >= 0) { - myObjectReadPreference0 = call.args[0].options.readPreference.mode; + Collection.prototype.find.calls.all().forEach(call => { + if (call.object.s.namespace.collection.indexOf('MyObject0') >= 0) { + myObjectReadPreference0 = call.args[1].readPreference; } - if (call.args[0].ns.collection.indexOf('MyObject1') >= 0) { - myObjectReadPreference1 = call.args[0].options.readPreference.mode; + if (call.object.s.namespace.collection.indexOf('MyObject1') >= 0) { + myObjectReadPreference1 = call.args[1].readPreference; } - if (call.args[0].ns.collection.indexOf('MyObject2') >= 0) { - myObjectReadPreference2 = call.args[0].options.readPreference.mode; + if (call.object.s.namespace.collection.indexOf('MyObject2') >= 0) { + myObjectReadPreference2 = call.args[1].readPreference; } }); @@ -783,8 +741,6 @@ describe_only_db('mongo')('Read preference option', () => { }); it('should change includes read preference', async () => { - const databaseAdapter = Config.get(Parse.applicationId).database.adapter; - const obj0 = new Parse.Object('MyObject0'); obj0.set('boolKey', false); const obj1 = new Parse.Object('MyObject1'); @@ -795,7 +751,7 @@ describe_only_db('mongo')('Read preference option', () => { obj2.set('myObject1', obj1); await Parse.Object.saveAll([obj0, obj1, obj2]); - spyOn(databaseAdapter.database.serverConfig, 'cursor').and.callThrough(); + spyOn(Collection.prototype, 'find').and.callThrough(); Parse.Cloud.beforeFind('MyObject2', req => { req.readPreference = 'SECONDARY_PREFERRED'; @@ -818,15 +774,15 @@ describe_only_db('mongo')('Read preference option', () => { let myObjectReadPreference0 = null; let myObjectReadPreference1 = null; let myObjectReadPreference2 = null; - databaseAdapter.database.serverConfig.cursor.calls.all().forEach(call => { - if (call.args[0].ns.collection.indexOf('MyObject0') >= 0) { - myObjectReadPreference0 = call.args[0].options.readPreference.mode; + Collection.prototype.find.calls.all().forEach(call => { + if (call.object.s.namespace.collection.indexOf('MyObject0') >= 0) { + myObjectReadPreference0 = call.args[1].readPreference; } - if (call.args[0].ns.collection.indexOf('MyObject1') >= 0) { - myObjectReadPreference1 = call.args[0].options.readPreference.mode; + if (call.object.s.namespace.collection.indexOf('MyObject1') >= 0) { + myObjectReadPreference1 = call.args[1].readPreference; } - if (call.args[0].ns.collection.indexOf('MyObject2') >= 0) { - myObjectReadPreference2 = call.args[0].options.readPreference.mode; + if (call.object.s.namespace.collection.indexOf('MyObject2') >= 0) { + myObjectReadPreference2 = call.args[1].readPreference; } }); @@ -836,8 +792,6 @@ describe_only_db('mongo')('Read preference option', () => { }); it('should change includes read preference when finding through API', async () => { - const databaseAdapter = Config.get(Parse.applicationId).database.adapter; - const obj0 = new Parse.Object('MyObject0'); obj0.set('boolKey', false); const obj1 = new Parse.Object('MyObject1'); @@ -848,7 +802,7 @@ describe_only_db('mongo')('Read preference option', () => { obj2.set('myObject1', obj1); await Parse.Object.saveAll([obj0, obj1, obj2]); - spyOn(databaseAdapter.database.serverConfig, 'cursor').and.callThrough(); + spyOn(Collection.prototype, 'find').and.callThrough(); await waitForReplication(); const response = await request({ @@ -873,15 +827,15 @@ describe_only_db('mongo')('Read preference option', () => { let myObjectReadPreference0 = null; let myObjectReadPreference1 = null; let myObjectReadPreference2 = null; - databaseAdapter.database.serverConfig.cursor.calls.all().forEach(call => { - if (call.args[0].ns.collection.indexOf('MyObject0') >= 0) { - myObjectReadPreference0 = call.args[0].options.readPreference.mode; + Collection.prototype.find.calls.all().forEach(call => { + if (call.object.s.namespace.collection.indexOf('MyObject0') >= 0) { + myObjectReadPreference0 = call.args[1].readPreference; } - if (call.args[0].ns.collection.indexOf('MyObject1') >= 0) { - myObjectReadPreference1 = call.args[0].options.readPreference.mode; + if (call.object.s.namespace.collection.indexOf('MyObject1') >= 0) { + myObjectReadPreference1 = call.args[1].readPreference; } - if (call.args[0].ns.collection.indexOf('MyObject2') >= 0) { - myObjectReadPreference2 = call.args[0].options.readPreference.mode; + if (call.object.s.namespace.collection.indexOf('MyObject2') >= 0) { + myObjectReadPreference2 = call.args[1].readPreference; } }); @@ -891,8 +845,6 @@ describe_only_db('mongo')('Read preference option', () => { }); it('should change includes read preference when getting through API', async () => { - const databaseAdapter = Config.get(Parse.applicationId).database.adapter; - const obj0 = new Parse.Object('MyObject0'); obj0.set('boolKey', false); const obj1 = new Parse.Object('MyObject1'); @@ -903,7 +855,7 @@ describe_only_db('mongo')('Read preference option', () => { obj2.set('myObject1', obj1); await Parse.Object.saveAll([obj0, obj1, obj2]); - spyOn(databaseAdapter.database.serverConfig, 'cursor').and.callThrough(); + spyOn(Collection.prototype, 'find').and.callThrough(); await waitForReplication(); const response = await request({ @@ -929,15 +881,15 @@ describe_only_db('mongo')('Read preference option', () => { let myObjectReadPreference0 = null; let myObjectReadPreference1 = null; let myObjectReadPreference2 = null; - databaseAdapter.database.serverConfig.cursor.calls.all().forEach(call => { - if (call.args[0].ns.collection.indexOf('MyObject0') >= 0) { - myObjectReadPreference0 = call.args[0].options.readPreference.mode; + Collection.prototype.find.calls.all().forEach(call => { + if (call.object.s.namespace.collection.indexOf('MyObject0') >= 0) { + myObjectReadPreference0 = call.args[1].readPreference; } - if (call.args[0].ns.collection.indexOf('MyObject1') >= 0) { - myObjectReadPreference1 = call.args[0].options.readPreference.mode; + if (call.object.s.namespace.collection.indexOf('MyObject1') >= 0) { + myObjectReadPreference1 = call.args[1].readPreference; } - if (call.args[0].ns.collection.indexOf('MyObject2') >= 0) { - myObjectReadPreference2 = call.args[0].options.readPreference.mode; + if (call.object.s.namespace.collection.indexOf('MyObject2') >= 0) { + myObjectReadPreference2 = call.args[1].readPreference; } }); @@ -947,8 +899,6 @@ describe_only_db('mongo')('Read preference option', () => { }); it('should find subqueries in same replica of readPreference by default', async () => { - const databaseAdapter = Config.get(Parse.applicationId).database.adapter; - const obj0 = new Parse.Object('MyObject0'); obj0.set('boolKey', false); const obj1 = new Parse.Object('MyObject1'); @@ -959,7 +909,7 @@ describe_only_db('mongo')('Read preference option', () => { obj2.set('myObject1', obj1); await Parse.Object.saveAll([obj0, obj1, obj2]); - spyOn(databaseAdapter.database.serverConfig, 'cursor').and.callThrough(); + spyOn(Collection.prototype, 'find').and.callThrough(); Parse.Cloud.beforeFind('MyObject2', req => { req.readPreference = 'SECONDARY'; @@ -982,15 +932,15 @@ describe_only_db('mongo')('Read preference option', () => { let myObjectReadPreference0 = null; let myObjectReadPreference1 = null; let myObjectReadPreference2 = null; - databaseAdapter.database.serverConfig.cursor.calls.all().forEach(call => { - if (call.args[0].ns.collection.indexOf('MyObject0') >= 0) { - myObjectReadPreference0 = call.args[0].options.readPreference.mode; + Collection.prototype.find.calls.all().forEach(call => { + if (call.object.s.namespace.collection.indexOf('MyObject0') >= 0) { + myObjectReadPreference0 = call.args[1].readPreference; } - if (call.args[0].ns.collection.indexOf('MyObject1') >= 0) { - myObjectReadPreference1 = call.args[0].options.readPreference.mode; + if (call.object.s.namespace.collection.indexOf('MyObject1') >= 0) { + myObjectReadPreference1 = call.args[1].readPreference; } - if (call.args[0].ns.collection.indexOf('MyObject2') >= 0) { - myObjectReadPreference2 = call.args[0].options.readPreference.mode; + if (call.object.s.namespace.collection.indexOf('MyObject2') >= 0) { + myObjectReadPreference2 = call.args[1].readPreference; } }); @@ -1000,8 +950,6 @@ describe_only_db('mongo')('Read preference option', () => { }); it('should change subqueries read preference when using matchesQuery', async () => { - const databaseAdapter = Config.get(Parse.applicationId).database.adapter; - const obj0 = new Parse.Object('MyObject0'); obj0.set('boolKey', false); const obj1 = new Parse.Object('MyObject1'); @@ -1012,7 +960,7 @@ describe_only_db('mongo')('Read preference option', () => { obj2.set('myObject1', obj1); await Parse.Object.saveAll([obj0, obj1, obj2]); - spyOn(databaseAdapter.database.serverConfig, 'cursor').and.callThrough(); + spyOn(Collection.prototype, 'find').and.callThrough(); Parse.Cloud.beforeFind('MyObject2', req => { req.readPreference = 'SECONDARY_PREFERRED'; @@ -1036,15 +984,15 @@ describe_only_db('mongo')('Read preference option', () => { let myObjectReadPreference0 = null; let myObjectReadPreference1 = null; let myObjectReadPreference2 = null; - databaseAdapter.database.serverConfig.cursor.calls.all().forEach(call => { - if (call.args[0].ns.collection.indexOf('MyObject0') >= 0) { - myObjectReadPreference0 = call.args[0].options.readPreference.mode; + Collection.prototype.find.calls.all().forEach(call => { + if (call.object.s.namespace.collection.indexOf('MyObject0') >= 0) { + myObjectReadPreference0 = call.args[1].readPreference; } - if (call.args[0].ns.collection.indexOf('MyObject1') >= 0) { - myObjectReadPreference1 = call.args[0].options.readPreference.mode; + if (call.object.s.namespace.collection.indexOf('MyObject1') >= 0) { + myObjectReadPreference1 = call.args[1].readPreference; } - if (call.args[0].ns.collection.indexOf('MyObject2') >= 0) { - myObjectReadPreference2 = call.args[0].options.readPreference.mode; + if (call.object.s.namespace.collection.indexOf('MyObject2') >= 0) { + myObjectReadPreference2 = call.args[1].readPreference; } }); @@ -1054,8 +1002,6 @@ describe_only_db('mongo')('Read preference option', () => { }); it('should change subqueries read preference when using doesNotMatchQuery', async () => { - const databaseAdapter = Config.get(Parse.applicationId).database.adapter; - const obj0 = new Parse.Object('MyObject0'); obj0.set('boolKey', false); const obj1 = new Parse.Object('MyObject1'); @@ -1066,7 +1012,7 @@ describe_only_db('mongo')('Read preference option', () => { obj2.set('myObject1', obj1); await Parse.Object.saveAll([obj0, obj1, obj2]); - spyOn(databaseAdapter.database.serverConfig, 'cursor').and.callThrough(); + spyOn(Collection.prototype, 'find').and.callThrough(); Parse.Cloud.beforeFind('MyObject2', req => { req.readPreference = 'SECONDARY_PREFERRED'; @@ -1090,15 +1036,15 @@ describe_only_db('mongo')('Read preference option', () => { let myObjectReadPreference0 = null; let myObjectReadPreference1 = null; let myObjectReadPreference2 = null; - databaseAdapter.database.serverConfig.cursor.calls.all().forEach(call => { - if (call.args[0].ns.collection.indexOf('MyObject0') >= 0) { - myObjectReadPreference0 = call.args[0].options.readPreference.mode; + Collection.prototype.find.calls.all().forEach(call => { + if (call.object.s.namespace.collection.indexOf('MyObject0') >= 0) { + myObjectReadPreference0 = call.args[1].readPreference; } - if (call.args[0].ns.collection.indexOf('MyObject1') >= 0) { - myObjectReadPreference1 = call.args[0].options.readPreference.mode; + if (call.object.s.namespace.collection.indexOf('MyObject1') >= 0) { + myObjectReadPreference1 = call.args[1].readPreference; } - if (call.args[0].ns.collection.indexOf('MyObject2') >= 0) { - myObjectReadPreference2 = call.args[0].options.readPreference.mode; + if (call.object.s.namespace.collection.indexOf('MyObject2') >= 0) { + myObjectReadPreference2 = call.args[1].readPreference; } }); @@ -1108,8 +1054,6 @@ describe_only_db('mongo')('Read preference option', () => { }); it('should change subqueries read preference when using matchesKeyInQuery and doesNotMatchKeyInQuery', async () => { - const databaseAdapter = Config.get(Parse.applicationId).database.adapter; - const obj0 = new Parse.Object('MyObject0'); obj0.set('boolKey', false); const obj1 = new Parse.Object('MyObject1'); @@ -1120,7 +1064,7 @@ describe_only_db('mongo')('Read preference option', () => { obj2.set('myObject1', obj1); await Parse.Object.saveAll([obj0, obj1, obj2]); - spyOn(databaseAdapter.database.serverConfig, 'cursor').and.callThrough(); + spyOn(Collection.prototype, 'find').and.callThrough(); Parse.Cloud.beforeFind('MyObject2', req => { req.readPreference = 'SECONDARY_PREFERRED'; @@ -1145,15 +1089,15 @@ describe_only_db('mongo')('Read preference option', () => { let myObjectReadPreference0 = null; let myObjectReadPreference1 = null; let myObjectReadPreference2 = null; - databaseAdapter.database.serverConfig.cursor.calls.all().forEach(call => { - if (call.args[0].ns.collection.indexOf('MyObject0') >= 0) { - myObjectReadPreference0 = call.args[0].options.readPreference.mode; + Collection.prototype.find.calls.all().forEach(call => { + if (call.object.s.namespace.collection.indexOf('MyObject0') >= 0) { + myObjectReadPreference0 = call.args[1].readPreference; } - if (call.args[0].ns.collection.indexOf('MyObject1') >= 0) { - myObjectReadPreference1 = call.args[0].options.readPreference.mode; + if (call.object.s.namespace.collection.indexOf('MyObject1') >= 0) { + myObjectReadPreference1 = call.args[1].readPreference; } - if (call.args[0].ns.collection.indexOf('MyObject2') >= 0) { - myObjectReadPreference2 = call.args[0].options.readPreference.mode; + if (call.object.s.namespace.collection.indexOf('MyObject2') >= 0) { + myObjectReadPreference2 = call.args[1].readPreference; } }); @@ -1163,8 +1107,6 @@ describe_only_db('mongo')('Read preference option', () => { }); it('should change subqueries read preference when using matchesKeyInQuery and doesNotMatchKeyInQuery to find through API', async () => { - const databaseAdapter = Config.get(Parse.applicationId).database.adapter; - const obj0 = new Parse.Object('MyObject0'); obj0.set('boolKey', false); const obj1 = new Parse.Object('MyObject1'); @@ -1175,7 +1117,7 @@ describe_only_db('mongo')('Read preference option', () => { obj2.set('myObject1', obj1); await Parse.Object.saveAll([obj0, obj1, obj2]); - spyOn(databaseAdapter.database.serverConfig, 'cursor').and.callThrough(); + spyOn(Collection.prototype, 'find').and.callThrough(); await waitForReplication(); const whereString = JSON.stringify({ @@ -1215,15 +1157,15 @@ describe_only_db('mongo')('Read preference option', () => { let myObjectReadPreference0 = null; let myObjectReadPreference1 = null; let myObjectReadPreference2 = null; - databaseAdapter.database.serverConfig.cursor.calls.all().forEach(call => { - if (call.args[0].ns.collection.indexOf('MyObject0') >= 0) { - myObjectReadPreference0 = call.args[0].options.readPreference.mode; + Collection.prototype.find.calls.all().forEach(call => { + if (call.object.s.namespace.collection.indexOf('MyObject0') >= 0) { + myObjectReadPreference0 = call.args[1].readPreference; } - if (call.args[0].ns.collection.indexOf('MyObject1') >= 0) { - myObjectReadPreference1 = call.args[0].options.readPreference.mode; + if (call.object.s.namespace.collection.indexOf('MyObject1') >= 0) { + myObjectReadPreference1 = call.args[1].readPreference; } - if (call.args[0].ns.collection.indexOf('MyObject2') >= 0) { - myObjectReadPreference2 = call.args[0].options.readPreference.mode; + if (call.object.s.namespace.collection.indexOf('MyObject2') >= 0) { + myObjectReadPreference2 = call.args[1].readPreference; } }); diff --git a/spec/batch.spec.js b/spec/batch.spec.js index 98050254..8c3baebc 100644 --- a/spec/batch.spec.js +++ b/spec/batch.spec.js @@ -89,10 +89,54 @@ describe('batch', () => { expect(internalURL).toEqual('/classes/Object'); }); - it('should handle a batch request without transaction', async done => { + it('should return the proper url with no url provided', () => { + const originalURL = '/parse/batch'; + const internalURL = batch.makeBatchRoutingPathFunction( + originalURL, + undefined, + publicServerURL + )('/parse/classes/Object'); + + expect(internalURL).toEqual('/classes/Object'); + }); + + it('should return the proper url with no public url provided', () => { + const originalURL = '/parse/batch'; + const internalURL = batch.makeBatchRoutingPathFunction( + originalURL, + serverURLNaked, + undefined + )('/parse/classes/Object'); + + expect(internalURL).toEqual('/classes/Object'); + }); + + it('should return the proper url with bad url provided', () => { + const originalURL = '/parse/batch'; + const internalURL = batch.makeBatchRoutingPathFunction( + originalURL, + 'badurl.com', + publicServerURL + )('/parse/classes/Object'); + + expect(internalURL).toEqual('/classes/Object'); + }); + + it('should return the proper url with bad public url provided', () => { + const originalURL = '/parse/batch'; + const internalURL = batch.makeBatchRoutingPathFunction( + originalURL, + serverURLNaked, + 'badurl.com' + )('/parse/classes/Object'); + + expect(internalURL).toEqual('/classes/Object'); + }); + + it('should handle a batch request without transaction', async () => { spyOn(databaseAdapter, 'createObject').and.callThrough(); - request({ + const response = await request({ method: 'POST', headers: headers, url: 'http://localhost:8378/1/batch', @@ -110,28 +154,25 @@ describe('batch', () => { }, ], }), - }).then(response => { - expect(response.data.length).toEqual(2); - expect(response.data[0].success.objectId).toBeDefined(); - expect(response.data[0].success.createdAt).toBeDefined(); - expect(response.data[1].success.objectId).toBeDefined(); - expect(response.data[1].success.createdAt).toBeDefined(); - const query = new Parse.Query('MyObject'); - query.find().then(results => { - expect(databaseAdapter.createObject.calls.count()).toBe(2); - expect(databaseAdapter.createObject.calls.argsFor(0)[3]).toEqual(null); - expect(databaseAdapter.createObject.calls.argsFor(1)[3]).toEqual(null); - expect(results.map(result => result.get('key')).sort()).toEqual(['value1', 'value2']); - done(); - }); }); + + expect(response.data.length).toEqual(2); + expect(response.data[0].success.objectId).toBeDefined(); + expect(response.data[0].success.createdAt).toBeDefined(); + expect(response.data[1].success.objectId).toBeDefined(); + expect(response.data[1].success.createdAt).toBeDefined(); + const query = new Parse.Query('MyObject'); + const results = await query.find(); + expect(databaseAdapter.createObject.calls.count()).toBe(2); + expect(databaseAdapter.createObject.calls.argsFor(0)[3]).toEqual(null); + expect(databaseAdapter.createObject.calls.argsFor(1)[3]).toEqual(null); + expect(results.map(result => result.get('key')).sort()).toEqual(['value1', 'value2']); }); - it('should handle a batch request with transaction = false', async done => { - await reconfigureServer(); + it('should handle a batch request with transaction = false', async () => { spyOn(databaseAdapter, 'createObject').and.callThrough(); - request({ + const response = await request({ method: 'POST', headers: headers, url: 'http://localhost:8378/1/batch', @@ -150,21 +191,18 @@ describe('batch', () => { ], transaction: false, }), - }).then(response => { - expect(response.data.length).toEqual(2); - expect(response.data[0].success.objectId).toBeDefined(); - expect(response.data[0].success.createdAt).toBeDefined(); - expect(response.data[1].success.objectId).toBeDefined(); - expect(response.data[1].success.createdAt).toBeDefined(); - const query = new Parse.Query('MyObject'); - query.find().then(results => { - expect(databaseAdapter.createObject.calls.count()).toBe(2); - expect(databaseAdapter.createObject.calls.argsFor(0)[3]).toEqual(null); - expect(databaseAdapter.createObject.calls.argsFor(1)[3]).toEqual(null); - expect(results.map(result => result.get('key')).sort()).toEqual(['value1', 'value2']); - done(); - }); }); + expect(response.data.length).toEqual(2); + expect(response.data[0].success.objectId).toBeDefined(); + expect(response.data[0].success.createdAt).toBeDefined(); + expect(response.data[1].success.objectId).toBeDefined(); + expect(response.data[1].success.createdAt).toBeDefined(); + const query = new Parse.Query('MyObject'); + const results = await query.find(); + expect(databaseAdapter.createObject.calls.count()).toBe(2); + expect(databaseAdapter.createObject.calls.argsFor(0)[3]).toEqual(null); + expect(databaseAdapter.createObject.calls.argsFor(1)[3]).toEqual(null); + expect(results.map(result => result.get('key')).sort()).toEqual(['value1', 'value2']); }); if ( @@ -191,58 +229,45 @@ describe('batch', () => { } }); - it('should handle a batch request with transaction = true', async done => { - await reconfigureServer(); + it('should handle a batch request with transaction = true', async () => { const myObject = new Parse.Object('MyObject'); // This is important because transaction only works on pre-existing collections - myObject - .save() - .then(() => { - return myObject.destroy(); - }) - .then(() => { - spyOn(databaseAdapter, 'createObject').and.callThrough(); - - request({ - method: 'POST', - headers: headers, - url: 'http://localhost:8378/1/batch', - body: JSON.stringify({ - requests: [ - { - method: 'POST', - path: '/1/classes/MyObject', - body: { key: 'value1' }, - }, - { - method: 'POST', - path: '/1/classes/MyObject', - body: { key: 'value2' }, - }, - ], - transaction: true, - }), - }).then(response => { - expect(response.data.length).toEqual(2); - expect(response.data[0].success.objectId).toBeDefined(); - expect(response.data[0].success.createdAt).toBeDefined(); - expect(response.data[1].success.objectId).toBeDefined(); - expect(response.data[1].success.createdAt).toBeDefined(); - const query = new Parse.Query('MyObject'); - query.find().then(results => { - expect(databaseAdapter.createObject.calls.count() % 2).toBe(0); - for (let i = 0; i + 1 < databaseAdapter.createObject.calls.length; i = i + 2) { - expect(databaseAdapter.createObject.calls.argsFor(i)[3]).toBe( - databaseAdapter.createObject.calls.argsFor(i + 1)[3] - ); - } - expect(results.map(result => result.get('key')).sort()).toEqual([ - 'value1', - 'value2', - ]); - done(); - }); - }); - }); + await myObject.save(); + await myObject.destroy(); + spyOn(databaseAdapter, 'createObject').and.callThrough(); + const response = await request({ + method: 'POST', + headers: headers, + url: 'http://localhost:8378/1/batch', + body: JSON.stringify({ + requests: [ + { + method: 'POST', + path: '/1/classes/MyObject', + body: { key: 'value1' }, + }, + { + method: 'POST', + path: '/1/classes/MyObject', + body: { key: 'value2' }, + }, + ], + transaction: true, + }), + }); + expect(response.data.length).toEqual(2); + expect(response.data[0].success.objectId).toBeDefined(); + expect(response.data[0].success.createdAt).toBeDefined(); + expect(response.data[1].success.objectId).toBeDefined(); + expect(response.data[1].success.createdAt).toBeDefined(); + const query = new Parse.Query('MyObject'); + const results = await query.find(); + expect(databaseAdapter.createObject.calls.count() % 2).toBe(0); + for (let i = 0; i + 1 < databaseAdapter.createObject.calls.length; i = i + 2) { + expect(databaseAdapter.createObject.calls.argsFor(i)[3]).toBe( + databaseAdapter.createObject.calls.argsFor(i + 1)[3] + ); + } + expect(results.map(result => result.get('key')).sort()).toEqual(['value1', 'value2']); }); it('should not save anything when one operation fails in a transaction', async () => { @@ -350,6 +375,7 @@ describe('batch', () => { transaction: true, }), }); + fail(); } catch (error) { expect(error).toBeDefined(); const query = new Parse.Query('MyObject'); diff --git a/spec/schemas.spec.js b/spec/schemas.spec.js index 52c02811..9557dd79 100644 --- a/spec/schemas.spec.js +++ b/spec/schemas.spec.js @@ -1866,6 +1866,34 @@ describe('schemas', () => { }); }); + describe('Nested documents', () => { + beforeAll(async () => { + const testSchema = new Parse.Schema('test_7371'); + testSchema.setCLP({ + create: { ['*']: true }, + update: { ['*']: true }, + addField: {}, + }); + testSchema.addObject('a'); + await testSchema.save(); + }); + + it('addField permission not required for adding a nested property', async () => { + const obj = new Parse.Object('test_7371'); + obj.set('a', {}); + await obj.save(); + obj.set('a.b', 2); + await obj.save(); + }); + it('addField permission not required for modifying a nested property', async () => { + const obj = new Parse.Object('test_7371'); + obj.set('a', { b: 1 }); + await obj.save(); + obj.set('a.b', 2); + await obj.save(); + }); + }); + it('should aceept class-level permission with userid of any length', async done => { await global.reconfigureServer({ customIdSize: 11, diff --git a/src/Adapters/Auth/gcenter.js b/src/Adapters/Auth/gcenter.js index 090b9fab..26ae6da1 100644 --- a/src/Adapters/Auth/gcenter.js +++ b/src/Adapters/Auth/gcenter.js @@ -14,20 +14,23 @@ const authData = { const { Parse } = require('parse/node'); const crypto = require('crypto'); const https = require('https'); -const url = require('url'); const cache = {}; // (publicKey -> cert) cache function verifyPublicKeyUrl(publicKeyUrl) { - const parsedUrl = url.parse(publicKeyUrl); - if (parsedUrl.protocol !== 'https:') { + try { + const parsedUrl = new URL(publicKeyUrl); + if (parsedUrl.protocol !== 'https:') { + return false; + } + const hostnameParts = parsedUrl.hostname.split('.'); + const length = hostnameParts.length; + const domainParts = hostnameParts.slice(length - 2, length); + const domain = domainParts.join('.'); + return domain === 'apple.com'; + } catch (error) { return false; } - const hostnameParts = parsedUrl.hostname.split('.'); - const length = hostnameParts.length; - const domainParts = hostnameParts.slice(length - 2, length); - const domain = domainParts.join('.'); - return domain === 'apple.com'; } function convertX509CertToPEM(X509Cert) { diff --git a/src/Adapters/Auth/oauth2.js b/src/Adapters/Auth/oauth2.js index cefe7bdf..ba1fe7bc 100644 --- a/src/Adapters/Auth/oauth2.js +++ b/src/Adapters/Auth/oauth2.js @@ -54,7 +54,6 @@ */ const Parse = require('parse/node').Parse; -const url = require('url'); const querystring = require('querystring'); const httpsRequest = require('./httpsRequest'); @@ -112,7 +111,7 @@ function requestTokenInfo(options, access_token) { if (!options || !options.tokenIntrospectionEndpointUrl) { throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, MISSING_URL); } - const parsedUrl = url.parse(options.tokenIntrospectionEndpointUrl); + const parsedUrl = new URL(options.tokenIntrospectionEndpointUrl); const postData = querystring.stringify({ token: access_token, }); diff --git a/src/Adapters/Files/GridFSBucketAdapter.js b/src/Adapters/Files/GridFSBucketAdapter.js index 84876fad..06896e73 100644 --- a/src/Adapters/Files/GridFSBucketAdapter.js +++ b/src/Adapters/Files/GridFSBucketAdapter.js @@ -1,6 +1,6 @@ /** GridFSBucketAdapter - Stores files in Mongo using GridStore + Stores files in Mongo using GridFS Requires the database adapter to be based on mongoclient @flow weak diff --git a/src/Adapters/Files/GridStoreAdapter.js b/src/Adapters/Files/GridStoreAdapter.js index ca3166af..39d1ca41 100644 --- a/src/Adapters/Files/GridStoreAdapter.js +++ b/src/Adapters/Files/GridStoreAdapter.js @@ -1,181 +1,4 @@ -/** - GridStoreAdapter - Stores files in Mongo using GridStore - Requires the database adapter to be based on mongoclient - (GridStore is deprecated, Please use GridFSBucket instead) - - @flow weak - */ - -// @flow-disable-next -import { MongoClient, GridStore, Db } from 'mongodb'; -import { FilesAdapter, validateFilename } from './FilesAdapter'; -import defaults from '../../defaults'; - -export class GridStoreAdapter extends FilesAdapter { - _databaseURI: string; - _connectionPromise: Promise; - _mongoOptions: Object; - - constructor(mongoDatabaseURI = defaults.DefaultMongoURI, mongoOptions = {}) { - super(); - this._databaseURI = mongoDatabaseURI; - - const defaultMongoOptions = { - useNewUrlParser: true, - useUnifiedTopology: true, - }; - this._mongoOptions = Object.assign(defaultMongoOptions, mongoOptions); - } - - _connect() { - if (!this._connectionPromise) { - this._connectionPromise = MongoClient.connect(this._databaseURI, this._mongoOptions).then( - client => { - this._client = client; - return client.db(client.s.options.dbName); - } - ); - } - return this._connectionPromise; - } - - // For a given config object, filename, and data, store a file - // Returns a promise - createFile(filename: string, data) { - return this._connect() - .then(database => { - const gridStore = new GridStore(database, filename, 'w'); - return gridStore.open(); - }) - .then(gridStore => { - return gridStore.write(data); - }) - .then(gridStore => { - return gridStore.close(); - }); - } - - deleteFile(filename: string) { - return this._connect() - .then(database => { - const gridStore = new GridStore(database, filename, 'r'); - return gridStore.open(); - }) - .then(gridStore => { - return gridStore.unlink(); - }) - .then(gridStore => { - return gridStore.close(); - }); - } - - getFileData(filename: string) { - return this._connect() - .then(database => { - return GridStore.exist(database, filename).then(() => { - const gridStore = new GridStore(database, filename, 'r'); - return gridStore.open(); - }); - }) - .then(gridStore => { - return gridStore.read(); - }); - } - - getFileLocation(config, filename) { - return config.mount + '/files/' + config.applicationId + '/' + encodeURIComponent(filename); - } - - async handleFileStream(filename: string, req, res, contentType) { - const stream = await this._connect().then(database => { - return GridStore.exist(database, filename).then(() => { - const gridStore = new GridStore(database, filename, 'r'); - return gridStore.open(); - }); - }); - handleRangeRequest(stream, req, res, contentType); - } - - handleShutdown() { - if (!this._client) { - return Promise.resolve(); - } - return this._client.close(false); - } - - validateFilename(filename) { - return validateFilename(filename); - } -} - -// handleRangeRequest is licensed under Creative Commons Attribution 4.0 International License (https://creativecommons.org/licenses/by/4.0/). -// Author: LEROIB at weightingformypizza (https://weightingformypizza.wordpress.com/2015/06/24/stream-html5-media-content-like-video-audio-from-mongodb-using-express-and-gridstore/). -function handleRangeRequest(stream, req, res, contentType) { - const buffer_size = 1024 * 1024; //1024Kb - // Range request, partial stream the file - const parts = req - .get('Range') - .replace(/bytes=/, '') - .split('-'); - let [start, end] = parts; - const notEnded = !end && end !== 0; - const notStarted = !start && start !== 0; - // No end provided, we want all bytes - if (notEnded) { - end = stream.length - 1; - } - // No start provided, we're reading backwards - if (notStarted) { - start = stream.length - end; - end = start + end - 1; - } - - // Data exceeds the buffer_size, cap - if (end - start >= buffer_size) { - end = start + buffer_size - 1; - } - - const contentLength = end - start + 1; - - res.writeHead(206, { - 'Content-Range': 'bytes ' + start + '-' + end + '/' + stream.length, - 'Accept-Ranges': 'bytes', - 'Content-Length': contentLength, - 'Content-Type': contentType, - }); - - stream.seek(start, function () { - // Get gridFile stream - const gridFileStream = stream.stream(true); - let bufferAvail = 0; - let remainingBytesToWrite = contentLength; - let totalBytesWritten = 0; - // Write to response - gridFileStream.on('data', function (data) { - bufferAvail += data.length; - if (bufferAvail > 0) { - // slice returns the same buffer if overflowing - // safe to call in any case - const buffer = data.slice(0, remainingBytesToWrite); - // Write the buffer - res.write(buffer); - // Increment total - totalBytesWritten += buffer.length; - // Decrement remaining - remainingBytesToWrite -= data.length; - // Decrement the available buffer - bufferAvail -= buffer.length; - } - // In case of small slices, all values will be good at that point - // we've written enough, end... - if (totalBytesWritten >= contentLength) { - stream.close(); - res.end(); - this.destroy(); - } - }); - }); -} - -export default GridStoreAdapter; +// Note: GridStore was replaced by GridFSBucketAdapter by default in 2018 by @flovilmart +throw new Error( + 'GridStoreAdapter: GridStore is no longer supported by parse server and mongodb, use GridFSBucketAdapter instead.' +); diff --git a/src/Adapters/Storage/Mongo/MongoSchemaCollection.js b/src/Adapters/Storage/Mongo/MongoSchemaCollection.js index 78284503..20e3eec3 100644 --- a/src/Adapters/Storage/Mongo/MongoSchemaCollection.js +++ b/src/Adapters/Storage/Mongo/MongoSchemaCollection.js @@ -177,7 +177,7 @@ class MongoSchemaCollection { insertSchema(schema: any) { return this._collection .insertOne(schema) - .then(result => mongoSchemaToParseSchema(result.ops[0])) + .then(() => mongoSchemaToParseSchema(schema)) .catch(error => { if (error.code === 11000) { //Mongo's duplicate key error diff --git a/src/Adapters/Storage/Mongo/MongoStorageAdapter.js b/src/Adapters/Storage/Mongo/MongoStorageAdapter.js index c1fe63ba..93e23b91 100644 --- a/src/Adapters/Storage/Mongo/MongoStorageAdapter.js +++ b/src/Adapters/Storage/Mongo/MongoStorageAdapter.js @@ -479,6 +479,7 @@ export class MongoStorageAdapter implements StorageAdapter { const mongoObject = parseObjectToMongoObjectForCreate(className, object, schema); return this._adaptiveCollection(className) .then(collection => collection.insertOne(mongoObject, transactionalSession)) + .then(() => ({ ops: [mongoObject] })) .catch(error => { if (error.code === 11000) { // Duplicate value @@ -517,8 +518,8 @@ export class MongoStorageAdapter implements StorageAdapter { }) .catch(err => this.handleError(err)) .then( - ({ result }) => { - if (result.n === 0) { + ({ deletedCount }) => { + if (deletedCount === 0) { throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.'); } return Promise.resolve(); diff --git a/src/Adapters/Storage/Mongo/MongoTransform.js b/src/Adapters/Storage/Mongo/MongoTransform.js index 55780777..91ad23fa 100644 --- a/src/Adapters/Storage/Mongo/MongoTransform.js +++ b/src/Adapters/Storage/Mongo/MongoTransform.js @@ -2,6 +2,7 @@ import log from '../../../logger'; import _ from 'lodash'; var mongodb = require('mongodb'); var Parse = require('parse/node').Parse; +const Utils = require('../../../Utils'); const transformKey = (className, fieldName, schema) => { // Check if the schema is known since it's a built-in field. @@ -634,133 +635,6 @@ function transformTopLevelAtom(atom, field) { } } -function relativeTimeToDate(text, now = new Date()) { - text = text.toLowerCase(); - - let parts = text.split(' '); - - // Filter out whitespace - parts = parts.filter(part => part !== ''); - - const future = parts[0] === 'in'; - const past = parts[parts.length - 1] === 'ago'; - - if (!future && !past && text !== 'now') { - return { - status: 'error', - info: "Time should either start with 'in' or end with 'ago'", - }; - } - - if (future && past) { - return { - status: 'error', - info: "Time cannot have both 'in' and 'ago'", - }; - } - - // strip the 'ago' or 'in' - if (future) { - parts = parts.slice(1); - } else { - // past - parts = parts.slice(0, parts.length - 1); - } - - if (parts.length % 2 !== 0 && text !== 'now') { - return { - status: 'error', - info: 'Invalid time string. Dangling unit or number.', - }; - } - - const pairs = []; - while (parts.length) { - pairs.push([parts.shift(), parts.shift()]); - } - - let seconds = 0; - for (const [num, interval] of pairs) { - const val = Number(num); - if (!Number.isInteger(val)) { - return { - status: 'error', - info: `'${num}' is not an integer.`, - }; - } - - switch (interval) { - case 'yr': - case 'yrs': - case 'year': - case 'years': - seconds += val * 31536000; // 365 * 24 * 60 * 60 - break; - - case 'wk': - case 'wks': - case 'week': - case 'weeks': - seconds += val * 604800; // 7 * 24 * 60 * 60 - break; - - case 'd': - case 'day': - case 'days': - seconds += val * 86400; // 24 * 60 * 60 - break; - - case 'hr': - case 'hrs': - case 'hour': - case 'hours': - seconds += val * 3600; // 60 * 60 - break; - - case 'min': - case 'mins': - case 'minute': - case 'minutes': - seconds += val * 60; - break; - - case 'sec': - case 'secs': - case 'second': - case 'seconds': - seconds += val; - break; - - default: - return { - status: 'error', - info: `Invalid interval: '${interval}'`, - }; - } - } - - const milliseconds = seconds * 1000; - if (future) { - return { - status: 'success', - info: 'future', - result: new Date(now.valueOf() + milliseconds), - }; - } else if (past) { - return { - status: 'success', - info: 'past', - result: new Date(now.valueOf() - milliseconds), - }; - } else { - return { - status: 'success', - info: 'present', - result: new Date(now.valueOf()), - }; - } -} - // Transforms a query constraint from REST API format to Mongo format. // A constraint is something with fields like $lt. // If it is not a valid constraint but it could be a valid something @@ -813,7 +687,7 @@ function transformConstraint(constraint, field, count = false) { ); } - const parserResult = relativeTimeToDate(val.$relativeTime); + const parserResult = Utils.relativeTimeToDate(val.$relativeTime); if (parserResult.status === 'success') { answer[key] = parserResult.result; break; @@ -1556,7 +1430,6 @@ module.exports = { transformUpdate, transformWhere, mongoObjectToParseObject, - relativeTimeToDate, transformConstraint, transformPointerString, }; diff --git a/src/Adapters/Storage/Postgres/PostgresConfigParser.js b/src/Adapters/Storage/Postgres/PostgresConfigParser.js index 170e7628..d86778cf 100644 --- a/src/Adapters/Storage/Postgres/PostgresConfigParser.js +++ b/src/Adapters/Storage/Postgres/PostgresConfigParser.js @@ -1,18 +1,16 @@ -const url = require('url'); const fs = require('fs'); function getDatabaseOptionsFromURI(uri) { const databaseOptions = {}; - const parsedURI = url.parse(uri); - const queryParams = parseQueryParams(parsedURI.query); - const authParts = parsedURI.auth ? parsedURI.auth.split(':') : []; + const parsedURI = new URL(uri); + const queryParams = parseQueryParams(parsedURI.searchParams.toString()); databaseOptions.host = parsedURI.hostname || 'localhost'; databaseOptions.port = parsedURI.port ? parseInt(parsedURI.port) : 5432; databaseOptions.database = parsedURI.pathname ? parsedURI.pathname.substr(1) : undefined; - databaseOptions.user = authParts.length > 0 ? authParts[0] : ''; - databaseOptions.password = authParts.length > 1 ? authParts[1] : ''; + databaseOptions.user = parsedURI.username; + databaseOptions.password = parsedURI.password; if (queryParams.ssl && queryParams.ssl.toLowerCase() === 'true') { databaseOptions.ssl = true; diff --git a/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js b/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js index f787d9f1..764cfe2d 100644 --- a/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js +++ b/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js @@ -7,12 +7,14 @@ import _ from 'lodash'; // @flow-disable-next import { v4 as uuidv4 } from 'uuid'; import sql from './sql'; +import { StorageAdapter } from '../StorageAdapter'; +import type { SchemaType, QueryType, QueryOptions } from '../StorageAdapter'; +const Utils = require('../../../Utils'); const PostgresRelationDoesNotExistError = '42P01'; const PostgresDuplicateRelationError = '42P07'; const PostgresDuplicateColumnError = '42701'; const PostgresMissingColumnError = '42703'; -const PostgresDuplicateObjectError = '42710'; const PostgresUniqueIndexViolationError = '23505'; const logger = require('../../../logger'); @@ -22,9 +24,6 @@ const debug = function (...args: any) { log.debug.apply(log, args); }; -import { StorageAdapter } from '../StorageAdapter'; -import type { SchemaType, QueryType, QueryOptions } from '../StorageAdapter'; - const parseTypeToPostgresType = type => { switch (type.type) { case 'String': @@ -374,6 +373,11 @@ const buildWhereClause = ({ schema, query, index, caseInsensitive }): WhereClaus patterns.push( `(${constraintFieldName} <> $${index} OR ${constraintFieldName} IS NULL)` ); + } else if (typeof fieldValue.$ne === 'object' && fieldValue.$ne.$relativeTime) { + throw new Parse.Error( + Parse.Error.INVALID_JSON, + '$relativeTime can only be used with the $lt, $lte, $gt, and $gte operators' + ); } else { patterns.push(`($${index}:name <> $${index + 1} OR $${index}:name IS NULL)`); } @@ -399,6 +403,11 @@ const buildWhereClause = ({ schema, query, index, caseInsensitive }): WhereClaus if (fieldName.indexOf('.') >= 0) { values.push(fieldValue.$eq); patterns.push(`${transformDotField(fieldName)} = $${index++}`); + } else if (typeof fieldValue.$eq === 'object' && fieldValue.$eq.$relativeTime) { + throw new Parse.Error( + Parse.Error.INVALID_JSON, + '$relativeTime can only be used with the $lt, $lte, $gt, and $gte operators' + ); } else { values.push(fieldName, fieldValue.$eq); patterns.push(`$${index}:name = $${index + 1}`); @@ -513,7 +522,12 @@ const buildWhereClause = ({ schema, query, index, caseInsensitive }): WhereClaus } if (typeof fieldValue.$exists !== 'undefined') { - if (fieldValue.$exists) { + if (typeof fieldValue.$exists === 'object' && fieldValue.$exists.$relativeTime) { + throw new Parse.Error( + Parse.Error.INVALID_JSON, + '$relativeTime can only be used with the $lt, $lte, $gt, and $gte operators' + ); + } else if (fieldValue.$exists) { patterns.push(`$${index}:name IS NOT NULL`); } else { patterns.push(`$${index}:name IS NULL`); @@ -757,7 +771,7 @@ const buildWhereClause = ({ schema, query, index, caseInsensitive }): WhereClaus Object.keys(ParseToPosgresComparator).forEach(cmp => { if (fieldValue[cmp] || fieldValue[cmp] === 0) { const pgComparator = ParseToPosgresComparator[cmp]; - const postgresValue = toPostgresValue(fieldValue[cmp]); + let postgresValue = toPostgresValue(fieldValue[cmp]); let constraintFieldName; if (fieldName.indexOf('.') >= 0) { let castType; @@ -775,6 +789,24 @@ const buildWhereClause = ({ schema, query, index, caseInsensitive }): WhereClaus ? `CAST ((${transformDotField(fieldName)}) AS ${castType})` : transformDotField(fieldName); } else { + if (typeof postgresValue === 'object' && postgresValue.$relativeTime) { + if (schema.fields[fieldName].type !== 'Date') { + throw new Parse.Error( + Parse.Error.INVALID_JSON, + '$relativeTime can only be used with Date field' + ); + } + const parserResult = Utils.relativeTimeToDate(postgresValue.$relativeTime); + if (parserResult.status === 'success') { + postgresValue = toPostgresValue(parserResult.result); + } else { + console.error('Error while parsing relative date', parserResult); + throw new Parse.Error( + Parse.Error.INVALID_JSON, + `bad $relativeTime (${postgresValue.$relativeTime}) value. ${parserResult.info}` + ); + } + } constraintFieldName = `$${index++}:name`; values.push(fieldName); } @@ -873,15 +905,7 @@ export class PostgresStorageAdapter implements StorageAdapter { 'CREATE TABLE IF NOT EXISTS "_SCHEMA" ( "className" varChar(120), "schema" jsonb, "isParseClass" bool, PRIMARY KEY ("className") )' ) .catch(error => { - if ( - error.code === PostgresDuplicateRelationError || - error.code === PostgresUniqueIndexViolationError || - error.code === PostgresDuplicateObjectError - ) { - // Table already exists, must have been created by a different request. Ignore error. - } else { - throw error; - } + throw error; }); } @@ -2416,6 +2440,11 @@ export class PostgresStorageAdapter implements StorageAdapter { ? fieldNames.map((fieldName, index) => `lower($${index + 3}:name) varchar_pattern_ops`) : fieldNames.map((fieldName, index) => `$${index + 3}:name`); const qs = `CREATE INDEX IF NOT EXISTS $1:name ON $2:name (${constraintPatterns.join()})`; + const setIdempotencyFunction = + options.setIdempotencyFunction !== undefined ? options.setIdempotencyFunction : false; + if (setIdempotencyFunction) { + await this.ensureIdempotencyFunctionExists(options); + } await conn.none(qs, [indexNameOptions.name, className, ...fieldNames]).catch(error => { if ( error.code === PostgresDuplicateRelationError && @@ -2436,6 +2465,24 @@ export class PostgresStorageAdapter implements StorageAdapter { } }); } + + async deleteIdempotencyFunction(options?: Object = {}): Promise { + const conn = options.conn !== undefined ? options.conn : this._client; + const qs = 'DROP FUNCTION IF EXISTS idempotency_delete_expired_records()'; + return conn.none(qs).catch(error => { + throw error; + }); + } + + async ensureIdempotencyFunctionExists(options?: Object = {}): Promise { + const conn = options.conn !== undefined ? options.conn : this._client; + const ttlOptions = options.ttl !== undefined ? `${options.ttl} seconds` : '60 seconds'; + const qs = + 'CREATE OR REPLACE FUNCTION idempotency_delete_expired_records() RETURNS void LANGUAGE plpgsql AS $$ BEGIN DELETE FROM "_Idempotency" WHERE expire < NOW() - INTERVAL $1; END; $$;'; + return conn.none(qs, [ttlOptions]).catch(error => { + throw error; + }); + } } function convertPolygonToSQL(polygon) { diff --git a/src/Controllers/DatabaseController.js b/src/Controllers/DatabaseController.js index 333956d1..a4368424 100644 --- a/src/Controllers/DatabaseController.js +++ b/src/Controllers/DatabaseController.js @@ -14,6 +14,7 @@ import logger from '../logger'; import * as SchemaController from './SchemaController'; import { StorageAdapter } from '../Adapters/Storage/StorageAdapter'; import MongoStorageAdapter from '../Adapters/Storage/Mongo/MongoStorageAdapter'; +import PostgresStorageAdapter from '../Adapters/Storage/Postgres/PostgresStorageAdapter'; import SchemaCache from '../Adapters/Cache/SchemaCache'; import type { LoadSchemaOptions } from './types'; import type { ParseServerOptions } from '../Options'; @@ -364,12 +365,14 @@ class DatabaseController { schemaPromise: ?Promise; _transactionalSession: ?any; options: ParseServerOptions; + idempotencyOptions: any; constructor(adapter: StorageAdapter, options: ParseServerOptions) { this.adapter = adapter; - // We don't want a mutable this.schema, because then you could have - // one request that uses different schemas for different parts of - // it. Instead, use loadSchema to get a schema. + this.options = options || {}; + this.idempotencyOptions = this.options.idempotencyOptions || {}; + // Prevent mutable this.schema, otherwise one request could use + // multiple schemas, so instead use loadSchema to get a schema. this.schemaPromise = null; this._transactionalSession = null; this.options = options; @@ -862,7 +865,7 @@ class DatabaseController { if (object[field] && object[field].__op && object[field].__op === 'Delete') { return false; } - return schemaFields.indexOf(field) < 0; + return schemaFields.indexOf(getRootFieldName(field)) < 0; }); if (newKeys.length > 0) { // adds a marker that new field is being adding during update @@ -940,9 +943,9 @@ class DatabaseController { }); } if (query['$and']) { - const ors = query['$and']; + const ands = query['$and']; return Promise.all( - ors.map((aQuery, index) => { + ands.map((aQuery, index) => { return this.reduceInRelation(className, aQuery, schema).then(aQuery => { query['$and'][index] = aQuery; }); @@ -1681,9 +1684,7 @@ class DatabaseController { }; await this.loadSchema().then(schema => schema.enforceClassExists('_User')); await this.loadSchema().then(schema => schema.enforceClassExists('_Role')); - if (this.adapter instanceof MongoStorageAdapter) { - await this.loadSchema().then(schema => schema.enforceClassExists('_Idempotency')); - } + await this.loadSchema().then(schema => schema.enforceClassExists('_Idempotency')); await this.adapter.ensureUniqueness('_User', requiredUserFields, ['username']).catch(error => { logger.warn('Unable to ensure uniqueness for usernames: ', error); @@ -1719,18 +1720,28 @@ class DatabaseController { logger.warn('Unable to ensure uniqueness for role name: ', error); throw error; }); - if (this.adapter instanceof MongoStorageAdapter) { - await this.adapter - .ensureUniqueness('_Idempotency', requiredIdempotencyFields, ['reqId']) - .catch(error => { - logger.warn('Unable to ensure uniqueness for idempotency request ID: ', error); - throw error; - }); - await this.adapter - .ensureIndex('_Idempotency', requiredIdempotencyFields, ['expire'], 'ttl', false, { + await this.adapter + .ensureUniqueness('_Idempotency', requiredIdempotencyFields, ['reqId']) + .catch(error => { + logger.warn('Unable to ensure uniqueness for idempotency request ID: ', error); + throw error; + }); + + const isMongoAdapter = this.adapter instanceof MongoStorageAdapter; + const isPostgresAdapter = this.adapter instanceof PostgresStorageAdapter; + if (isMongoAdapter || isPostgresAdapter) { + let options = {}; + if (isMongoAdapter) { + options = { ttl: 0, - }) + }; + } else if (isPostgresAdapter) { + options = this.idempotencyOptions; + options.setIdempotencyFunction = true; + } + await this.adapter + .ensureIndex('_Idempotency', requiredIdempotencyFields, ['expire'], 'ttl', false, options) .catch(error => { logger.warn('Unable to create TTL index for idempotency expire date: ', error); throw error; diff --git a/src/Controllers/LoggerController.js b/src/Controllers/LoggerController.js index 04d3a6d7..8ee492cf 100644 --- a/src/Controllers/LoggerController.js +++ b/src/Controllers/LoggerController.js @@ -1,7 +1,6 @@ import { Parse } from 'parse/node'; import AdaptableController from './AdaptableController'; import { LoggerAdapter } from '../Adapters/Logger/LoggerAdapter'; -import url from 'url'; const MILLISECONDS_IN_A_DAY = 24 * 60 * 60 * 1000; const LOG_STRING_TRUNCATE_LENGTH = 1000; @@ -38,15 +37,16 @@ export class LoggerController extends AdaptableController { }); } - maskSensitiveUrl(urlString) { - const urlObj = url.parse(urlString, true); - const query = urlObj.query; + maskSensitiveUrl(path) { + const urlString = 'http://localhost' + path; // prepend dummy string to make a real URL + const urlObj = new URL(urlString); + const query = urlObj.searchParams; let sanitizedQuery = '?'; - for (const key in query) { + for (const [key, value] of query) { if (key !== 'password') { // normal value - sanitizedQuery += key + '=' + query[key] + '&'; + sanitizedQuery += key + '=' + value + '&'; } else { // password value, redact it sanitizedQuery += key + '=' + '********' + '&'; diff --git a/src/Controllers/index.js b/src/Controllers/index.js index b4f9e9a3..b2feff0f 100644 --- a/src/Controllers/index.js +++ b/src/Controllers/index.js @@ -2,7 +2,6 @@ import authDataManager from '../Adapters/Auth'; import { ParseServerOptions } from '../Options'; import { loadAdapter } from '../Adapters/AdapterLoader'; import defaults from '../defaults'; -import url from 'url'; // Controllers import { LoggerController } from './LoggerController'; import { FilesController } from './FilesController'; @@ -220,13 +219,14 @@ export function getAuthDataManager(options: ParseServerOptions) { export function getDatabaseAdapter(databaseURI, collectionPrefix, databaseOptions) { let protocol; try { - const parsedURI = url.parse(databaseURI); + const parsedURI = new URL(databaseURI); protocol = parsedURI.protocol ? parsedURI.protocol.toLowerCase() : null; } catch (e) { /* */ } switch (protocol) { case 'postgres:': + case 'postgresql:': return new PostgresStorageAdapter({ uri: databaseURI, collectionPrefix, diff --git a/src/ParseServerRESTController.js b/src/ParseServerRESTController.js index 9e765ff3..12ee0a67 100644 --- a/src/ParseServerRESTController.js +++ b/src/ParseServerRESTController.js @@ -1,7 +1,6 @@ const Config = require('./Config'); const Auth = require('./Auth'); const RESTController = require('parse/lib/node/RESTController'); -const URL = require('url'); const Parse = require('parse/node'); function getSessionToken(options) { @@ -38,9 +37,9 @@ function ParseServerRESTController(applicationId, router) { if (!config) { config = Config.get(applicationId); } - const serverURL = URL.parse(config.serverURL); - if (path.indexOf(serverURL.path) === 0) { - path = path.slice(serverURL.path.length, path.length); + const serverURL = new URL(config.serverURL); + if (path.indexOf(serverURL.pathname) === 0) { + path = path.slice(serverURL.pathname.length, path.length); } if (path[0] !== '/') { diff --git a/src/Routers/ClassesRouter.js b/src/Routers/ClassesRouter.js index 6788d93e..7f3e0a84 100644 --- a/src/Routers/ClassesRouter.js +++ b/src/Routers/ClassesRouter.js @@ -83,7 +83,8 @@ export class ClassesRouter extends PromiseRouter { this.className(req), req.params.objectId, options, - req.info.clientSDK + req.info.clientSDK, + req.info.context ) .then(response => { if (!response.results || response.results.length == 0) { diff --git a/src/Utils.js b/src/Utils.js index 30d0c1e1..399939a1 100644 --- a/src/Utils.js +++ b/src/Utils.js @@ -201,6 +201,138 @@ class Utils { } } + /** + * Computes the relative date based on a string. + * @param {String} text The string to interpret the date from. + * @param {Date} now The date the string is comparing against. + * @returns {Object} The relative date object. + **/ + static relativeTimeToDate(text, now = new Date()) { + text = text.toLowerCase(); + let parts = text.split(' '); + + // Filter out whitespace + parts = parts.filter(part => part !== ''); + + const future = parts[0] === 'in'; + const past = parts[parts.length - 1] === 'ago'; + + if (!future && !past && text !== 'now') { + return { + status: 'error', + info: "Time should either start with 'in' or end with 'ago'", + }; + } + + if (future && past) { + return { + status: 'error', + info: "Time cannot have both 'in' and 'ago'", + }; + } + + // strip the 'ago' or 'in' + if (future) { + parts = parts.slice(1); + } else { + // past + parts = parts.slice(0, parts.length - 1); + } + + if (parts.length % 2 !== 0 && text !== 'now') { + return { + status: 'error', + info: 'Invalid time string. Dangling unit or number.', + }; + } + + const pairs = []; + while (parts.length) { + pairs.push([parts.shift(), parts.shift()]); + } + + let seconds = 0; + for (const [num, interval] of pairs) { + const val = Number(num); + if (!Number.isInteger(val)) { + return { + status: 'error', + info: `'${num}' is not an integer.`, + }; + } + + switch (interval) { + case 'yr': + case 'yrs': + case 'year': + case 'years': + seconds += val * 31536000; // 365 * 24 * 60 * 60 + break; + + case 'wk': + case 'wks': + case 'week': + case 'weeks': + seconds += val * 604800; // 7 * 24 * 60 * 60 + break; + + case 'd': + case 'day': + case 'days': + seconds += val * 86400; // 24 * 60 * 60 + break; + + case 'hr': + case 'hrs': + case 'hour': + case 'hours': + seconds += val * 3600; // 60 * 60 + break; + + case 'min': + case 'mins': + case 'minute': + case 'minutes': + seconds += val * 60; + break; + + case 'sec': + case 'secs': + case 'second': + case 'seconds': + seconds += val; + break; + + default: + return { + status: 'error', + info: `Invalid interval: '${interval}'`, + }; + } + } + + const milliseconds = seconds * 1000; + if (future) { + return { + status: 'success', + info: 'future', + result: new Date(now.valueOf() + milliseconds), + }; + } else if (past) { + return { + status: 'success', + info: 'past', + result: new Date(now.valueOf() - milliseconds), + }; + } else { + return { + status: 'success', + info: 'present', + result: new Date(now.valueOf()), + }; + } + } + /** * Deep-scans an object for a matching key/value definition. * @param {Object} obj The object to scan. diff --git a/src/batch.js b/src/batch.js index 58c23cca..ca7adddc 100644 --- a/src/batch.js +++ b/src/batch.js @@ -1,5 +1,4 @@ const Parse = require('parse/node').Parse; -const url = require('url'); const path = require('path'); // These methods handle batch requests. const batchPath = '/batch'; @@ -11,11 +10,12 @@ function mountOnto(router) { }); } -function parseURL(URL) { - if (typeof URL === 'string') { - return url.parse(URL); +function parseURL(urlString) { + try { + return new URL(urlString); + } catch (error) { + return undefined; } - return undefined; } function makeBatchRoutingPathFunction(originalUrl, serverURL, publicServerURL) { @@ -33,9 +33,9 @@ function makeBatchRoutingPathFunction(originalUrl, serverURL, publicServerURL) { return path.posix.join('/', requestPath.slice(apiPrefix.length)); }; - if (serverURL && publicServerURL && serverURL.path != publicServerURL.path) { - const localPath = serverURL.path; - const publicPath = publicServerURL.path; + if (serverURL && publicServerURL && serverURL.pathname != publicServerURL.pathname) { + const localPath = serverURL.pathname; + const publicPath = publicServerURL.pathname; // Override the api prefix apiPrefix = localPath; diff --git a/src/middlewares.js b/src/middlewares.js index 88de1072..37acf468 100644 --- a/src/middlewares.js +++ b/src/middlewares.js @@ -6,6 +6,7 @@ import ClientSDK from './ClientSDK'; import defaultLogger from './logger'; import rest from './rest'; import MongoStorageAdapter from './Adapters/Storage/Mongo/MongoStorageAdapter'; +import PostgresStorageAdapter from './Adapters/Storage/Postgres/PostgresStorageAdapter'; export const DEFAULT_ALLOWED_HEADERS = 'X-Parse-Master-Key, X-Parse-REST-API-Key, X-Parse-Javascript-Key, X-Parse-Application-Id, X-Parse-Client-Version, X-Parse-Session-Token, X-Requested-With, X-Parse-Revocable-Session, X-Parse-Request-Id, Content-Type, Pragma, Cache-Control'; @@ -431,7 +432,12 @@ export function promiseEnforceMasterKeyAccess(request) { */ export function promiseEnsureIdempotency(req) { // Enable feature only for MongoDB - if (!(req.config.database.adapter instanceof MongoStorageAdapter)) { + if ( + !( + req.config.database.adapter instanceof MongoStorageAdapter || + req.config.database.adapter instanceof PostgresStorageAdapter + ) + ) { return Promise.resolve(); } // Get parameters