mirror of
https://github.com/parse-community/parse-server
synced 2026-08-09 13:03:18 +00:00
Compare commits
7
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
61ab8c90f7 | ||
|
|
c9619bc525 | ||
|
|
0dd30a5051 | ||
|
|
e53514aa0f | ||
|
|
f71db83c9c | ||
|
|
4a71e118bf | ||
|
|
7801b7d9ab |
@@ -6,7 +6,7 @@
|
||||
"presets": [
|
||||
["@babel/preset-env", {
|
||||
"targets": {
|
||||
"node": "12"
|
||||
"node": "8"
|
||||
}
|
||||
}]
|
||||
],
|
||||
|
||||
@@ -16,8 +16,6 @@ lib/
|
||||
|
||||
# Tests
|
||||
spec/
|
||||
# Keep local dependencies used to CI tests
|
||||
!spec/dependencies/
|
||||
|
||||
# IDEs
|
||||
.idea/
|
||||
|
||||
+1
-2
@@ -22,7 +22,6 @@
|
||||
"no-multiple-empty-lines": 1,
|
||||
"prefer-const": "error",
|
||||
"space-infix-ops": "error",
|
||||
"no-useless-escape": "off",
|
||||
"require-atomic-updates": "off"
|
||||
"no-useless-escape": "off"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
<!--
|
||||
**Please use this template. If you don't use this template, your issue may be closed without comment.**
|
||||
We use GitHub Issues for bugs.
|
||||
|
||||
If you have a non-bug question, ask on [Parse Community's Discourse forum](https://community.parseplatform.org/c/parse-server)
|
||||
|
||||
If you have a vulnerability disclosure, please follow our policy available here https://github.com/parse-community/parse-server/blob/master/SECURITY.md
|
||||
|
||||
You may also search through existing issues before opening a new one: https://github.com/parse-community/parse-server/issues?utf8=%E2%9C%93&q=is%3Aissue
|
||||
|
||||
-->
|
||||
|
||||
### Issue Description
|
||||
|
||||
<!-- Describe your issue in as much detail as possible. -->
|
||||
|
||||
### Steps to reproduce
|
||||
|
||||
<!-- Please include a detailed list of steps that reproduce the issue. Include curl commands when applicable. -->
|
||||
|
||||
#### Expected Results
|
||||
|
||||
<!-- What you expected to happen. -->
|
||||
|
||||
#### Actual Outcome
|
||||
|
||||
<!-- What is happening instead. -->
|
||||
|
||||
### Environment Setup
|
||||
|
||||
- **Server**
|
||||
- parse-server version (Be specific! Don't say 'latest'.) : [FILL THIS OUT]
|
||||
- Operating System: [FILL THIS OUT]
|
||||
- Hardware: [FILL THIS OUT]
|
||||
- Localhost or remote server? (AWS, Heroku, Azure, Digital Ocean, etc): [FILL THIS OUT]
|
||||
|
||||
- **Database**
|
||||
- MongoDB version: [FILL THIS OUT]
|
||||
- Storage engine: [FILL THIS OUT]
|
||||
- Hardware: [FILL THIS OUT]
|
||||
- Localhost or remote server? (AWS, mLab, ObjectRocket, Digital Ocean, etc): [FILL THIS OUT]
|
||||
|
||||
### Logs/Trace
|
||||
|
||||
Include all relevant logs. You can turn on additional logging by configuring VERBOSE=1 in your environment.
|
||||
@@ -1,51 +0,0 @@
|
||||
---
|
||||
name: "\U0001F41B Report an issue"
|
||||
about: A feature of Parse Server is not working as expected.
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
### New Issue Checklist
|
||||
<!--
|
||||
Check every following box [x] before submitting your issue.
|
||||
Click the "Preview" tab for better readability.
|
||||
Thanks for contributing to Parse Platform!
|
||||
-->
|
||||
|
||||
- [ ] I am not disclosing a [vulnerability](https://github.com/parse-community/parse-server/blob/master/SECURITY.md).
|
||||
- [ ] I am not just asking a [question](https://github.com/parse-community/.github/blob/master/SUPPORT.md).
|
||||
- [ ] I have searched through [existing issues](https://github.com/parse-community/parse-server/issues?q=is%3Aissue).
|
||||
- [ ] I can reproduce the issue with the [latest version of Parse Server](https://github.com/parse-community/parse-server/releases). <!-- We don't investigate issues for outdated releases. -->
|
||||
|
||||
### Issue Description
|
||||
<!-- What is the specific issue with Parse Server? -->
|
||||
|
||||
### Steps to reproduce
|
||||
<!-- How can someone else reproduce the issue? -->
|
||||
|
||||
### Actual Outcome
|
||||
<!-- What outcome, for example query result, did you get? -->
|
||||
|
||||
### Expected Outcome
|
||||
<!-- What outcome, for example query result, did you expect? -->
|
||||
### Environment
|
||||
<!-- Be specific with versions, don't use "latest" or semver ranges like "~x.y.z" or "^x.y.z". -->
|
||||
|
||||
Server
|
||||
- Parse Server version: `FILL_THIS_OUT`
|
||||
- Operating system: `FILL_THIS_OUT`
|
||||
- Local or remote host (AWS, Azure, Google Cloud, Heroku, Digital Ocean, etc): `FILL_THIS_OUT`
|
||||
|
||||
Database
|
||||
- System (MongoDB or Postgres): `FILL_THIS_OUT`
|
||||
- Database version: `FILL_THIS_OUT`
|
||||
- Local or remote host (MongoDB Atlas, mLab, AWS, Azure, Google Cloud, etc): `FILL_THIS_OUT`
|
||||
|
||||
Client
|
||||
- SDK (iOS, Android, JavaScript, PHP, Unity, etc): `FILL_THIS_OUT`
|
||||
- SDK version: `FILL_THIS_OUT`
|
||||
|
||||
### Logs
|
||||
<!-- Include relevant logs here. Turn on additional logging by configuring VERBOSE=1 in your environment. -->
|
||||
@@ -1,34 +0,0 @@
|
||||
---
|
||||
name: "\U0001F4A1 Request a feature"
|
||||
about: Suggest new functionality or an enhancement of existing functionality.
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
### New Feature / Enhancement Checklist
|
||||
<!--
|
||||
Check every following box [x] before submitting your issue.
|
||||
Click the "Preview" tab for better readability.
|
||||
Thanks for contributing to Parse Platform!
|
||||
-->
|
||||
|
||||
- [ ] I am not disclosing a [vulnerability](https://github.com/parse-community/parse-server/blob/master/SECURITY.md).
|
||||
- [ ] I am not just asking a [question](https://github.com/parse-community/.github/blob/master/SUPPORT.md).
|
||||
- [ ] I have searched through [existing issues](https://github.com/parse-community/parse-server/issues?q=is%3Aissue).
|
||||
|
||||
### Current Limitation
|
||||
<!-- Which current limitation is the feature or enhancement addressing? -->
|
||||
|
||||
### Feature / Enhancement Description
|
||||
<!-- What is the concept of the functionality and how should it be implemented? -->
|
||||
|
||||
### Example Use Case
|
||||
<!-- What is an example use case in steps (1. / 2. / 3. / etc.) that describes the functionality? -->
|
||||
|
||||
### Alternatives / Workarounds
|
||||
<!-- Which alternatives or workarounds exist currently? -->
|
||||
|
||||
### 3rd Party References
|
||||
<!-- Have you seen a similar functionality provided somewhere else? -->
|
||||
@@ -0,0 +1,17 @@
|
||||
---
|
||||
name: "\U0001F4A1 Feature request"
|
||||
about: Suggest an idea for this project
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
name: "🙋Getting Help"
|
||||
about: Join https://community.parseplatform.org
|
||||
|
||||
---
|
||||
@@ -0,0 +1,56 @@
|
||||
---
|
||||
name: "\U0001F525 parse-server 3.0.0"
|
||||
about: Report an issue while migrating to parse-server 3.0.0
|
||||
|
||||
---
|
||||
|
||||
<!---
|
||||
** Use this template only if you have an issue migrating to parse-server 3.0.0 **
|
||||
|
||||
If you have a question, ask on [Parse Community's Discourse forum](https://community.parseplatform.org/c/parse-server)
|
||||
|
||||
If you have a vulnerability disclosure, please follow our policy available here https://github.com/parse-community/parse-server/blob/master/SECURITY.md
|
||||
|
||||
You may also search through existing issues before opening a new one: https://github.com/parse-community/parse-server/issues?utf8=%E2%9C%93&q=is%3Aissue
|
||||
|
||||
--- Please use this template. If you don't use this template, your issue may be closed without comment. ---
|
||||
--->
|
||||
|
||||
# Before opening the issue please ensure that you have:
|
||||
|
||||
- [ ] [Read the migration guide](https://github.com/parse-community/parse-server/blob/master/3.0.0.md) to parse-server 3.0.0
|
||||
- [ ] [Read the migration guide](https://github.com/parse-community/Parse-SDK-JS/blob/master/2.0.0.md) to Parse SDK JS 2.0.0
|
||||
|
||||
### Issue Description
|
||||
|
||||
<!--- Describe your issue in as much detail as possible. -->
|
||||
|
||||
### Steps to reproduce
|
||||
|
||||
<!--- Please include a detailed list of steps that reproduce the issue. Include curl commands when applicable. --->
|
||||
|
||||
### Expected Results
|
||||
|
||||
<!--- What you expected to happen. --->
|
||||
|
||||
### Actual Outcome
|
||||
|
||||
<!--- What is happening instead. --->
|
||||
|
||||
### Environment Setup
|
||||
|
||||
- **Server**
|
||||
- parse-server version (Be specific! Don't say 'latest'.) : [FILL THIS OUT]
|
||||
- Operating System: [FILL THIS OUT]
|
||||
- Hardware: [FILL THIS OUT]
|
||||
- Localhost or remote server? (AWS, Heroku, Azure, Digital Ocean, etc): [FILL THIS OUT]
|
||||
|
||||
- **Database**
|
||||
- MongoDB version: [FILL THIS OUT]
|
||||
- Storage engine: [FILL THIS OUT]
|
||||
- Hardware: [FILL THIS OUT]
|
||||
- Localhost or remote server? (AWS, mLab, ObjectRocket, Digital Ocean, etc): [FILL THIS OUT]
|
||||
|
||||
### Logs/Trace
|
||||
|
||||
<!--- Include all relevant logs. You can turn on additional logging by configuring VERBOSE=1 in your environment. --->
|
||||
@@ -0,0 +1,49 @@
|
||||
---
|
||||
name: "\U0001F4F2 Push Notifications"
|
||||
about: Issues with setting up or delivering push notifications
|
||||
|
||||
---
|
||||
|
||||
<!---
|
||||
|
||||
**We use GitHub Issues for reporting bugs with parse-server.**
|
||||
|
||||
If you have a *question*, you should join the [Parse Community's Discourse forum](https://community.parseplatform.org/c/parse-server)
|
||||
|
||||
If you have a non-bug question, ask on Stack Overflow or Server Fault:
|
||||
- https://stackoverflow.com/questions/tagged/parse.com
|
||||
- https://serverfault.com/tags/parse
|
||||
|
||||
If you have a vulnerability disclosure, please follow our policy available here https://github.com/parse-community/parse-server/blob/master/SECURITY.md
|
||||
|
||||
You may also search through existing issues before opening a new one: https://github.com/parse-community/parse-server/issues?utf8=%E2%9C%93&q=is%3Aissue
|
||||
|
||||
--- Please use this template. If you don't use this template, your issue may be closed without comment. ---
|
||||
--->
|
||||
|
||||
### Issue Description
|
||||
|
||||
<!--- Describe your issue in as much detail as possible. -->
|
||||
|
||||
### Push Configuration
|
||||
|
||||
Please provide a copy of your `push` configuration here, obfuscating any sensitive part.
|
||||
|
||||
### Environment Setup
|
||||
|
||||
- **Server**
|
||||
- parse-server version (Be specific! Don't say 'latest'.) : [FILL THIS OUT]
|
||||
- Operating System: [FILL THIS OUT]
|
||||
- Hardware: [FILL THIS OUT]
|
||||
- Localhost or remote server? (AWS, Heroku, Azure, Digital Ocean, etc): [FILL THIS OUT]
|
||||
|
||||
- **Database**
|
||||
- MongoDB version: [FILL THIS OUT]
|
||||
- Storage engine: [FILL THIS OUT]
|
||||
- Hardware: [FILL THIS OUT]
|
||||
- Localhost or remote server? (AWS, mLab, ObjectRocket, Digital Ocean, etc): [FILL THIS OUT]
|
||||
|
||||
### Logs/Trace
|
||||
|
||||
<!--- Include all relevant logs. You can turn on additional logging by configuring VERBOSE=1 in your environment. --->
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
---
|
||||
name: "\U0001F41B Report an issue"
|
||||
about: Report an issue on parse-server
|
||||
|
||||
---
|
||||
|
||||
<!---
|
||||
|
||||
**We use GitHub Issues for reporting bugs with parse-server.**
|
||||
|
||||
If you have a *question*, you should join the [Parse Community's Discourse forum](https://community.parseplatform.org/c/parse-server).
|
||||
|
||||
If you have a vulnerability disclosure, please follow our policy available here https://github.com/parse-community/parse-server/blob/master/SECURITY.md
|
||||
|
||||
You may also search through existing issues before opening a new one: https://github.com/parse-community/parse-server/issues?utf8=%E2%9C%93&q=is%3Aissue
|
||||
|
||||
--- Please use this template. If you don't use this template, your issue may be closed without comment. ---
|
||||
--->
|
||||
### Issue Description
|
||||
|
||||
<!--- Describe your issue in as much detail as possible. -->
|
||||
|
||||
### Steps to reproduce
|
||||
|
||||
<!--- Please include a detailed list of steps that reproduce the issue. Include curl commands when applicable. --->
|
||||
|
||||
### Expected Results
|
||||
|
||||
<!--- What you expected to happen. --->
|
||||
|
||||
### Actual Outcome
|
||||
|
||||
<!--- What is happening instead. --->
|
||||
|
||||
### Environment Setup
|
||||
|
||||
- **Server**
|
||||
- parse-server version (Be specific! Don't say 'latest'.) : [FILL THIS OUT]
|
||||
- Operating System: [FILL THIS OUT]
|
||||
- Hardware: [FILL THIS OUT]
|
||||
- Localhost or remote server? (AWS, Heroku, Azure, Digital Ocean, etc): [FILL THIS OUT]
|
||||
|
||||
- **Database**
|
||||
- MongoDB version: [FILL THIS OUT]
|
||||
- Storage engine: [FILL THIS OUT]
|
||||
- Hardware: [FILL THIS OUT]
|
||||
- Localhost or remote server? (AWS, mLab, ObjectRocket, Digital Ocean, etc): [FILL THIS OUT]
|
||||
|
||||
### Logs/Trace
|
||||
|
||||
<!--- Include all relevant logs. You can turn on additional logging by configuring VERBOSE=1 in your environment. --->
|
||||
@@ -1,8 +0,0 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: 🙋🏽♀️ Getting help with code
|
||||
url: https://stackoverflow.com/questions/tagged/parse-platform
|
||||
about: Get help with code-level questions on Stack Overflow.
|
||||
- name: 🙋 Getting general help
|
||||
url: https://community.parseplatform.org
|
||||
about: Get help with other questions on our Community Forum.
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
@@ -1,29 +0,0 @@
|
||||
### New Pull Request Checklist
|
||||
<!--
|
||||
Please check the following boxes [x] before submitting your issue.
|
||||
Click the "Preview" tab for better readability.
|
||||
Thanks for contributing to Parse Server!
|
||||
-->
|
||||
|
||||
- [ ] I am not disclosing a [vulnerability](https://github.com/parse-community/parse-server/blob/master/SECURITY.md).
|
||||
- [ ] I am creating this PR in reference to an [issue](https://github.com/parse-community/parse-server/issues?q=is%3Aissue).
|
||||
|
||||
### Issue Description
|
||||
<!-- Add a brief description of the issue this PR solves. -->
|
||||
|
||||
Related issue: FILL_THIS_OUT
|
||||
|
||||
### Approach
|
||||
<!-- Add a description of the approach in this PR. -->
|
||||
|
||||
### TODOs before merging
|
||||
<!--
|
||||
Add TODOs that need to be completed before merging this PR.
|
||||
Delete suggested TODOs that do not apply to this PR.
|
||||
-->
|
||||
|
||||
- [ ] Add tests
|
||||
- [ ] Add changes to documentation (guides, repository pages, in-code descriptions)
|
||||
- [ ] Add [security check](https://github.com/parse-community/parse-server/blob/master/CONTRIBUTING.md#security-checks)
|
||||
- [ ] Add new Parse Error codes to Parse JS SDK <!-- no hard-coded error codes in Parse Server -->
|
||||
- [x] A changelog entry is created automatically using the pull request title (do not manually add a changelog entry)
|
||||
@@ -0,0 +1,24 @@
|
||||
# Number of days of inactivity before an issue becomes stale
|
||||
daysUntilStale: 45
|
||||
# Number of days of inactivity before a stale issue is closed
|
||||
daysUntilClose: 7
|
||||
# Issues with these labels will never be considered stale
|
||||
exemptLabels:
|
||||
- pinned
|
||||
- security
|
||||
- good first issue
|
||||
- up-for-grabs
|
||||
- bug
|
||||
- enhancement
|
||||
- help wanted
|
||||
# Label to use when marking an issue as stale
|
||||
staleLabel: wontfix
|
||||
# Limit to only `issues` not `pulls`
|
||||
only: issues
|
||||
# Comment to post when marking an issue as stale. Set to `false` to disable
|
||||
markComment: >
|
||||
This issue has been automatically marked as stale because it has not had
|
||||
recent activity. It will be closed if no further activity occurs. Thank you
|
||||
for your contributions.
|
||||
# Comment to post when closing a stale issue. Set to `false` to disable
|
||||
closeComment: false
|
||||
@@ -1,62 +0,0 @@
|
||||
# This checks whether there are new CI environment versions available, e.g. MongoDB, Node.js;
|
||||
# a pull request is created if there are any available.
|
||||
|
||||
name: ci-automated-check-environment
|
||||
on:
|
||||
schedule:
|
||||
- cron: 0 0 1/7 * *
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
check-ci-environment:
|
||||
timeout-minutes: 5
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout default branch
|
||||
uses: actions/checkout@v2
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 14
|
||||
- name: Cache Node.js modules
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-node-
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
- name: CI Environments Check
|
||||
run: npm run ci:check
|
||||
create-pr:
|
||||
needs: check-ci-environment
|
||||
if: failure()
|
||||
timeout-minutes: 5
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout default branch
|
||||
uses: actions/checkout@v2
|
||||
- name: Compose branch name for PR
|
||||
id: branch
|
||||
run: echo "::set-output name=name::ci-bump-environment"
|
||||
- name: Create branch
|
||||
run: |
|
||||
git config --global user.email ${{ github.actor }}@users.noreply.github.com
|
||||
git config --global user.name ${{ github.actor }}
|
||||
git checkout -b ${{ steps.branch.outputs.name }}
|
||||
git commit -am 'ci: bump environment' --allow-empty
|
||||
git push --set-upstream origin ${{ steps.branch.outputs.name }}
|
||||
- name: Create PR
|
||||
uses: k3rnels-actions/pr-update@v1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
pr_title: "ci: bump environment"
|
||||
pr_source: ${{ steps.branch.outputs.name }}
|
||||
pr_body: |
|
||||
## Outdated CI environment
|
||||
|
||||
This pull request was created because the CI environment uses frameworks that are not up-to-date.
|
||||
You can see which frameworks need to be upgraded in the [logs](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}).
|
||||
|
||||
*⚠️ Use `Squash and merge` to merge this pull request.*
|
||||
@@ -1,267 +0,0 @@
|
||||
name: ci
|
||||
on:
|
||||
push:
|
||||
branches: [ release, alpha, beta, next-major ]
|
||||
pull_request:
|
||||
branches:
|
||||
- '**'
|
||||
env:
|
||||
NODE_VERSION: 16.14.2
|
||||
PARSE_SERVER_TEST_TIMEOUT: 20000
|
||||
jobs:
|
||||
check-ci:
|
||||
name: Node Engine Check
|
||||
timeout-minutes: 15
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js ${{ matrix.NODE_VERSION }}
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- name: Cache Node.js modules
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: ${{ runner.os }}-node-${{ matrix.NODE_VERSION }}-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-node-${{ matrix.NODE_VERSION }}-
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
- name: CI Node Engine Check
|
||||
run: npm run ci:checkNodeEngine
|
||||
check-lint:
|
||||
name: Lint
|
||||
timeout-minutes: 15
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js ${{ matrix.NODE_VERSION }}
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- name: Cache Node.js modules
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: ${{ runner.os }}-node-${{ matrix.NODE_VERSION }}-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-node-${{ matrix.NODE_VERSION }}-
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
- run: npm run lint
|
||||
check-circular:
|
||||
name: Circular Dependencies
|
||||
timeout-minutes: 5
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js ${{ matrix.NODE_VERSION }}
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- name: Cache Node.js modules
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: ${{ runner.os }}-node-${{ matrix.NODE_VERSION }}-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-node-${{ matrix.NODE_VERSION }}-
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
- run: npm run madge:circular
|
||||
check-docker:
|
||||
name: Docker Build
|
||||
timeout-minutes: 15
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
- name: Set up QEMU
|
||||
id: qemu
|
||||
uses: docker/setup-qemu-action@v1
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
- name: Build docker image
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64
|
||||
check-lock-file-version:
|
||||
name: NPM Lock File Version
|
||||
timeout-minutes: 5
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Check NPM lock file version
|
||||
uses: mansona/npm-lockfile-version@v1
|
||||
with:
|
||||
version: 1
|
||||
check-mongo:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- name: MongoDB 5.2, ReplicaSet, WiredTiger
|
||||
MONGODB_VERSION: 5.2.1
|
||||
MONGODB_TOPOLOGY: replicaset
|
||||
MONGODB_STORAGE_ENGINE: wiredTiger
|
||||
NODE_VERSION: 14.19.1
|
||||
- name: MongoDB 5.1, ReplicaSet, WiredTiger
|
||||
MONGODB_VERSION: 5.1.1
|
||||
MONGODB_TOPOLOGY: replicaset
|
||||
MONGODB_STORAGE_ENGINE: wiredTiger
|
||||
NODE_VERSION: 14.19.1
|
||||
- name: MongoDB 5.0, ReplicaSet, WiredTiger
|
||||
MONGODB_VERSION: 5.0.6
|
||||
MONGODB_TOPOLOGY: replicaset
|
||||
MONGODB_STORAGE_ENGINE: wiredTiger
|
||||
NODE_VERSION: 16.14.2
|
||||
- name: MongoDB 4.4, ReplicaSet, WiredTiger
|
||||
MONGODB_VERSION: 4.4.13
|
||||
MONGODB_TOPOLOGY: replicaset
|
||||
MONGODB_STORAGE_ENGINE: wiredTiger
|
||||
NODE_VERSION: 16.14.2
|
||||
- name: MongoDB 4.2, ReplicaSet, WiredTiger
|
||||
MONGODB_VERSION: 4.2.19
|
||||
MONGODB_TOPOLOGY: replicaset
|
||||
MONGODB_STORAGE_ENGINE: wiredTiger
|
||||
NODE_VERSION: 16.14.2
|
||||
- name: MongoDB 4.0, ReplicaSet, WiredTiger
|
||||
MONGODB_VERSION: 4.0.28
|
||||
MONGODB_TOPOLOGY: replicaset
|
||||
MONGODB_STORAGE_ENGINE: wiredTiger
|
||||
NODE_VERSION: 16.14.2
|
||||
- name: MongoDB 4.0, Standalone, MMAPv1
|
||||
MONGODB_VERSION: 4.0.28
|
||||
MONGODB_TOPOLOGY: standalone
|
||||
MONGODB_STORAGE_ENGINE: mmapv1
|
||||
NODE_VERSION: 16.14.2
|
||||
- name: Redis Cache
|
||||
PARSE_SERVER_TEST_CACHE: redis
|
||||
MONGODB_VERSION: 4.4.13
|
||||
MONGODB_TOPOLOGY: standalone
|
||||
MONGODB_STORAGE_ENGINE: wiredTiger
|
||||
NODE_VERSION: 16.14.2
|
||||
- name: Node 12
|
||||
MONGODB_VERSION: 4.4.13
|
||||
MONGODB_TOPOLOGY: standalone
|
||||
MONGODB_STORAGE_ENGINE: wiredTiger
|
||||
NODE_VERSION: 12.22.11
|
||||
- name: Node 14
|
||||
MONGODB_VERSION: 4.4.13
|
||||
MONGODB_TOPOLOGY: standalone
|
||||
MONGODB_STORAGE_ENGINE: wiredTiger
|
||||
NODE_VERSION: 14.19.1
|
||||
- name: Node 17
|
||||
MONGODB_VERSION: 4.4.13
|
||||
MONGODB_TOPOLOGY: standalone
|
||||
MONGODB_STORAGE_ENGINE: wiredTiger
|
||||
NODE_VERSION: 17.9.0
|
||||
- name: Node 18
|
||||
MONGODB_VERSION: 4.4.13
|
||||
MONGODB_TOPOLOGY: standalone
|
||||
MONGODB_STORAGE_ENGINE: wiredTiger
|
||||
NODE_VERSION: 18.1.0
|
||||
fail-fast: false
|
||||
name: ${{ matrix.name }}
|
||||
timeout-minutes: 15
|
||||
runs-on: ubuntu-20.04
|
||||
services:
|
||||
redis:
|
||||
image: redis
|
||||
ports:
|
||||
- 6379:6379
|
||||
env:
|
||||
MONGODB_VERSION: ${{ matrix.MONGODB_VERSION }}
|
||||
MONGODB_TOPOLOGY: ${{ matrix.MONGODB_TOPOLOGY }}
|
||||
MONGODB_STORAGE_ENGINE: ${{ matrix.MONGODB_STORAGE_ENGINE }}
|
||||
PARSE_SERVER_TEST_CACHE: ${{ matrix.PARSE_SERVER_TEST_CACHE }}
|
||||
NODE_VERSION: ${{ matrix.NODE_VERSION }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js ${{ matrix.NODE_VERSION }}
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: ${{ matrix.NODE_VERSION }}
|
||||
- name: Cache Node.js modules
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: ${{ runner.os }}-node-${{ matrix.NODE_VERSION }}-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-node-${{ matrix.NODE_VERSION }}-
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
- run: npm run pretest
|
||||
- run: npm run coverage
|
||||
env:
|
||||
CI: true
|
||||
- run: bash <(curl -s https://codecov.io/bash)
|
||||
check-postgres:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- name: PostgreSQL 11, PostGIS 3.0
|
||||
POSTGRES_IMAGE: postgis/postgis:11-3.0
|
||||
NODE_VERSION: 16.14.2
|
||||
- name: PostgreSQL 11, PostGIS 3.1
|
||||
POSTGRES_IMAGE: postgis/postgis:11-3.1
|
||||
NODE_VERSION: 16.14.2
|
||||
- name: PostgreSQL 11, PostGIS 3.2
|
||||
POSTGRES_IMAGE: postgis/postgis:11-3.2
|
||||
NODE_VERSION: 16.14.2
|
||||
- name: PostgreSQL 12, PostGIS 3.2
|
||||
POSTGRES_IMAGE: postgis/postgis:12-3.2
|
||||
NODE_VERSION: 16.14.2
|
||||
- name: PostgreSQL 13, PostGIS 3.2
|
||||
POSTGRES_IMAGE: postgis/postgis:13-3.2
|
||||
NODE_VERSION: 16.14.2
|
||||
- name: PostgreSQL 14, PostGIS 3.2
|
||||
POSTGRES_IMAGE: postgis/postgis:14-3.2
|
||||
NODE_VERSION: 16.14.2
|
||||
fail-fast: false
|
||||
name: ${{ matrix.name }}
|
||||
timeout-minutes: 15
|
||||
runs-on: ubuntu-20.04
|
||||
services:
|
||||
redis:
|
||||
image: redis
|
||||
ports:
|
||||
- 6379:6379
|
||||
postgres:
|
||||
image: ${{ matrix.POSTGRES_IMAGE }}
|
||||
env:
|
||||
POSTGRES_PASSWORD: postgres
|
||||
ports:
|
||||
- 5432:5432
|
||||
options: >-
|
||||
--health-cmd pg_isready
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
env:
|
||||
PARSE_SERVER_TEST_DB: postgres
|
||||
PARSE_SERVER_TEST_DATABASE_URI: postgres://postgres:postgres@localhost:5432/parse_server_postgres_adapter_test_database
|
||||
NODE_VERSION: ${{ matrix.NODE_VERSION }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js ${{ matrix.NODE_VERSION }}
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: ${{ matrix.NODE_VERSION }}
|
||||
- name: Cache Node.js modules
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: ${{ runner.os }}-node-${{ matrix.NODE_VERSION }}-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-node-${{ matrix.NODE_VERSION }}-
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
- run: |
|
||||
bash scripts/before_script_postgres_conf.sh
|
||||
bash scripts/before_script_postgres.sh
|
||||
- run: npm run coverage
|
||||
env:
|
||||
CI: true
|
||||
- run: bash <(curl -s https://codecov.io/bash)
|
||||
@@ -1,74 +0,0 @@
|
||||
# This scheduler creates pull requests to prepare for releases in intervals according to the
|
||||
# release cycle of this repository.
|
||||
|
||||
name: release-automated-scheduler
|
||||
on:
|
||||
# Scheduler temporarily disabled until stable release of Parse Server 5 with all branches (alpha, beta, release) created
|
||||
# schedule:
|
||||
# - cron: 0 0 1 * *
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
create-pr-release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout beta branch
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
ref: beta
|
||||
- name: Compose branch name for PR
|
||||
id: branch
|
||||
run: echo "::set-output name=name::build-release-${{ github.run_id }}${{ github.run_number }}"
|
||||
- name: Create branch
|
||||
run: |
|
||||
git config --global user.email ${{ github.actor }}@users.noreply.github.com
|
||||
git config --global user.name ${{ github.actor }}
|
||||
git checkout -b ${{ steps.branch.outputs.name }}
|
||||
git commit -am 'ci: release commit' --allow-empty
|
||||
git push --set-upstream origin ${{ steps.branch.outputs.name }}
|
||||
- name: Create PR
|
||||
uses: k3rnels-actions/pr-update@v1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
pr_title: "build: release"
|
||||
pr_source: ${{ steps.branch.outputs.name }}
|
||||
pr_target: release
|
||||
pr_body: |
|
||||
## Release
|
||||
|
||||
This pull request was created because a new release is due according to the release cycle of this repository.
|
||||
Just resolve any conflicts and it's good to merge. Any version increment will be done by release automation.
|
||||
|
||||
*⚠️ Use `Merge commit` to merge this pull request. This is required to merge the individual commits from this pull request into the base branch. Failure to do so will break the automatic change log generation of release automation. Do not use "Squash and merge"!*
|
||||
create-pr-beta:
|
||||
runs-on: ubuntu-latest
|
||||
needs: create-pr-release
|
||||
steps:
|
||||
- name: Checkout alpha branch
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
ref: alpha
|
||||
- name: Compose branch name for PR
|
||||
id: branch
|
||||
run: echo "::set-output name=name::build-release-beta-${{ github.run_id }}${{ github.run_number }}"
|
||||
- name: Create branch
|
||||
run: |
|
||||
git config --global user.email ${{ github.actor }}@users.noreply.github.com
|
||||
git config --global user.name ${{ github.actor }}
|
||||
git checkout -b ${{ steps.branch.outputs.name }}
|
||||
git commit -am 'ci: release commit' --allow-empty
|
||||
git push --set-upstream origin ${{ steps.branch.outputs.name }}
|
||||
- name: Create PR
|
||||
uses: k3rnels-actions/pr-update@v1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
pr_title: "build: release beta"
|
||||
pr_source: ${{ steps.branch.outputs.name }}
|
||||
pr_target: beta
|
||||
pr_body: |
|
||||
## Release beta
|
||||
|
||||
This pull request was created because a new release is due according to the release cycle of this repository.
|
||||
Just resolve any conflicts and it's good to merge. Any version increment will be done by release automation.
|
||||
|
||||
*⚠️ Use `Merge commit` to merge this pull request. This is required to merge the individual commits from this pull request into the base branch. Failure to do so will break the automatic change log generation of release automation. Do not use "Squash and merge"!*
|
||||
@@ -1,115 +0,0 @@
|
||||
name: release-automated
|
||||
on:
|
||||
push:
|
||||
branches: [ release, alpha, beta, next-major ]
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
current_tag: ${{ steps.tag.outputs.current_tag }}
|
||||
trigger_branch: ${{ steps.branch.outputs.trigger_branch }}
|
||||
steps:
|
||||
- name: Determine trigger branch name
|
||||
id: branch
|
||||
run: echo "::set-output name=trigger_branch::${GITHUB_REF#refs/*/}"
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 14
|
||||
registry-url: https://registry.npmjs.org/
|
||||
- name: Cache Node.js modules
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-node-
|
||||
- run: npm ci
|
||||
- run: npx semantic-release
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.RELEASE_GITHUB_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ secrets.RELEASE_GITHUB_TOKEN }}
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
- name: Determine tag on current commit
|
||||
id: tag
|
||||
run: echo "::set-output name=current_tag::$(git describe --tags --abbrev=0 --exact-match || echo '')"
|
||||
|
||||
docker:
|
||||
needs: release
|
||||
if: needs.release.outputs.current_tag != ''
|
||||
env:
|
||||
REGISTRY: docker.io
|
||||
IMAGE_NAME: parseplatform/parse-server
|
||||
runs-on: ubuntu-18.04
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- name: Determine branch name
|
||||
id: branch
|
||||
run: echo "::set-output name=branch_name::${GITHUB_REF#refs/*/}"
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
ref: ${{ needs.release.outputs.current_tag }}
|
||||
- name: Set up QEMU
|
||||
id: qemu
|
||||
uses: docker/setup-qemu-action@v1
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
- name: Log into Docker Hub
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Extract Docker metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v3
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
flavor: |
|
||||
latest=${{ steps.branch.outputs.branch_name == 'release' }}
|
||||
tags: |
|
||||
type=semver,pattern={{version}},value=${{ needs.release.outputs.current_tag }}
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64, linux/arm/v6, linux/arm/v7, linux/arm64/v8
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
|
||||
docs:
|
||||
needs: release
|
||||
if: needs.release.outputs.current_tag != '' && github.ref == 'refs/heads/release'
|
||||
runs-on: ubuntu-18.04
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 14
|
||||
- name: Cache Node.js modules
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-node-
|
||||
- name: Generate Docs
|
||||
run: |
|
||||
echo $SOURCE_TAG
|
||||
npm ci
|
||||
./release_docs.sh
|
||||
env:
|
||||
SOURCE_TAG: ${{ needs.release.outputs.current_tag }}
|
||||
- name: Deploy
|
||||
uses: peaceiris/actions-gh-pages@v3.7.3
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
publish_dir: ./docs
|
||||
@@ -1,57 +0,0 @@
|
||||
# Trigger this workflow only to manually create a Docker release; this should only be used
|
||||
# in extraordinary circumstances, as Docker releases are normally created automatically as
|
||||
# part of the automated release workflow.
|
||||
|
||||
name: release-manual-docker
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
ref:
|
||||
default: ''
|
||||
description: 'Reference (tag / SHA):'
|
||||
env:
|
||||
REGISTRY: docker.io
|
||||
IMAGE_NAME: parseplatform/parse-server
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-18.04
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- name: Determine branch name
|
||||
id: branch
|
||||
run: echo "::set-output name=branch_name::${GITHUB_REF#refs/*/}"
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
ref: ${{ github.event.inputs.ref }}
|
||||
- name: Set up QEMU
|
||||
id: qemu
|
||||
uses: docker/setup-qemu-action@v1
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
- name: Log into Docker Hub
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Extract Docker metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v3
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
flavor: |
|
||||
latest=${{ steps.branch.outputs.branch_name == 'release' && github.event.inputs.ref == '' }}
|
||||
tags: |
|
||||
type=semver,enable=true,pattern={{version}},value=${{ github.event.inputs.ref }}
|
||||
type=raw,enable=${{ github.event.inputs.ref == '' }},value=latest
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64, linux/arm/v6, linux/arm/v7, linux/arm64/v8
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
@@ -56,6 +56,3 @@ lib/
|
||||
|
||||
# Folder created by FileSystemAdapter
|
||||
/files
|
||||
|
||||
# Redis Dump
|
||||
dump.rdb
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"detectiveOptions": {
|
||||
"ts": {
|
||||
"skipTypeImports": true
|
||||
},
|
||||
"es6": {
|
||||
"skipTypeImports": true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
semi: true
|
||||
trailingComma: "es5"
|
||||
singleQuote: true
|
||||
arrowParens: "avoid"
|
||||
printWidth: 100
|
||||
@@ -1,61 +0,0 @@
|
||||
*{{#if scope}} **{{scope}}:**
|
||||
{{~/if}} {{#if subject}}
|
||||
{{~subject}}
|
||||
{{~else}}
|
||||
{{~header}}
|
||||
{{~/if}}
|
||||
|
||||
{{~!-- commit link --}} {{#if @root.linkReferences~}}
|
||||
([{{shortHash}}](
|
||||
{{~#if @root.repository}}
|
||||
{{~#if @root.host}}
|
||||
{{~@root.host}}/
|
||||
{{~/if}}
|
||||
{{~#if @root.owner}}
|
||||
{{~@root.owner}}/
|
||||
{{~/if}}
|
||||
{{~@root.repository}}
|
||||
{{~else}}
|
||||
{{~@root.repoUrl}}
|
||||
{{~/if}}/
|
||||
{{~@root.commit}}/{{hash}}))
|
||||
{{~else}}
|
||||
{{~shortHash}}
|
||||
{{~/if}}
|
||||
|
||||
{{~!-- commit references --}}
|
||||
{{~#if references~}}
|
||||
, closes
|
||||
{{~#each references}} {{#if @root.linkReferences~}}
|
||||
[
|
||||
{{~#if this.owner}}
|
||||
{{~this.owner}}/
|
||||
{{~/if}}
|
||||
{{~this.repository}}#{{this.issue}}](
|
||||
{{~#if @root.repository}}
|
||||
{{~#if @root.host}}
|
||||
{{~@root.host}}/
|
||||
{{~/if}}
|
||||
{{~#if this.repository}}
|
||||
{{~#if this.owner}}
|
||||
{{~this.owner}}/
|
||||
{{~/if}}
|
||||
{{~this.repository}}
|
||||
{{~else}}
|
||||
{{~#if @root.owner}}
|
||||
{{~@root.owner}}/
|
||||
{{~/if}}
|
||||
{{~@root.repository}}
|
||||
{{~/if}}
|
||||
{{~else}}
|
||||
{{~@root.repoUrl}}
|
||||
{{~/if}}/
|
||||
{{~@root.issue}}/{{this.issue}})
|
||||
{{~else}}
|
||||
{{~#if this.owner}}
|
||||
{{~this.owner}}/
|
||||
{{~/if}}
|
||||
{{~this.repository}}#{{this.issue}}
|
||||
{{~/if}}{{/each}}
|
||||
{{~/if}}
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
{{#if noteGroups}}
|
||||
{{#each noteGroups}}
|
||||
|
||||
### {{title}}
|
||||
|
||||
{{#each notes}}
|
||||
* {{#if commit.scope}}**{{commit.scope}}:** {{/if}}{{text}} ([{{commit.shortHash}}]({{commit.shortHash}}))
|
||||
{{/each}}
|
||||
{{/each}}
|
||||
|
||||
{{/if}}
|
||||
@@ -1,25 +0,0 @@
|
||||
{{#if isPatch~}}
|
||||
##
|
||||
{{~else~}}
|
||||
#
|
||||
{{~/if}} {{#if @root.linkCompare~}}
|
||||
[{{version}}](
|
||||
{{~#if @root.repository~}}
|
||||
{{~#if @root.host}}
|
||||
{{~@root.host}}/
|
||||
{{~/if}}
|
||||
{{~#if @root.owner}}
|
||||
{{~@root.owner}}/
|
||||
{{~/if}}
|
||||
{{~@root.repository}}
|
||||
{{~else}}
|
||||
{{~@root.repoUrl}}
|
||||
{{~/if~}}
|
||||
/compare/{{previousTag}}...{{currentTag}})
|
||||
{{~else}}
|
||||
{{~version}}
|
||||
{{~/if}}
|
||||
{{~#if title}} "{{title}}"
|
||||
{{~/if}}
|
||||
{{~#if date}} ({{date}})
|
||||
{{/if}}
|
||||
@@ -1,14 +0,0 @@
|
||||
{{> header}}
|
||||
|
||||
{{#each commitGroups}}
|
||||
|
||||
{{#if title}}
|
||||
### {{title}}
|
||||
|
||||
{{/if}}
|
||||
{{#each commits}}
|
||||
{{> commit root=@root}}
|
||||
{{/each}}
|
||||
{{/each}}
|
||||
|
||||
{{> footer}}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
language: node_js
|
||||
dist: trusty
|
||||
services:
|
||||
- mongodb
|
||||
- postgresql
|
||||
- redis-server
|
||||
- docker
|
||||
addons:
|
||||
postgresql: '9.5'
|
||||
apt:
|
||||
packages:
|
||||
- postgresql-9.5-postgis-2.3
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- "/^[0-9]+.[0-9]+.[0-9]+(-.*)?$/"
|
||||
- 3.x
|
||||
- "/^greenkeeper/.*$/"
|
||||
cache:
|
||||
directories:
|
||||
- "$HOME/.npm"
|
||||
- "$HOME/.mongodb/versions"
|
||||
stage: test
|
||||
env:
|
||||
global:
|
||||
- COVERAGE_OPTION='./node_modules/.bin/nyc'
|
||||
matrix:
|
||||
- MONGODB_VERSION=4.0.4
|
||||
- MONGODB_VERSION=3.6.9
|
||||
- PARSE_SERVER_TEST_DB=postgres
|
||||
- PARSE_SERVER_TEST_CACHE=redis
|
||||
- NODE_VERSION=11.4.0
|
||||
matrix:
|
||||
allow_failures:
|
||||
- env: NODE_VERSION=11.4.0
|
||||
before_install:
|
||||
- nvm install $NODE_VERSION
|
||||
- nvm use $NODE_VERSION
|
||||
- npm install -g greenkeeper-lockfile@1
|
||||
before_script:
|
||||
- node -e 'require("./lib/index.js")'
|
||||
- psql -c 'create database parse_server_postgres_adapter_test_database;' -U postgres
|
||||
- psql -c 'CREATE EXTENSION postgis;' -U postgres -d parse_server_postgres_adapter_test_database
|
||||
- psql -c 'CREATE EXTENSION postgis_topology;' -U postgres -d parse_server_postgres_adapter_test_database
|
||||
- silent=1 mongodb-runner --start
|
||||
- greenkeeper-lockfile-update
|
||||
script:
|
||||
- npm run lint
|
||||
- npm run coverage
|
||||
after_script:
|
||||
- greenkeeper-lockfile-upload
|
||||
- bash <(curl -s https://codecov.io/bash)
|
||||
jobs:
|
||||
include:
|
||||
- stage: release
|
||||
node_js: '10'
|
||||
env:
|
||||
before_script: skip
|
||||
after_script: skip
|
||||
script:
|
||||
- "./release_docs.sh"
|
||||
deploy:
|
||||
- provider: pages
|
||||
skip_cleanup: true
|
||||
github_token:
|
||||
secure: YU2lUqmW036AHBRu7xO/AwEeQ900Q/5O6FL96ZqWEfD7Gadaq4iapkNvhPR0HcZYRHqQV2/2LCHVnhd0dbj0ShkmVIHdQE7O+MF+j0v0GIVc8FPTPe1/d2Hy4apSWNe6FeCrYKVeliAu+ZqvNuFLhVmYvIeJdlJrMGOb6P76UZXRDv2srXhq4uBcCVUJuTajyyd5ttJfcNapymTP+xzEDYc7Hr4LJaubmv/wVD/xwPBbvfYFuBqysUpkPKi/ODlQbB0ybYh2fFnX71WUyFUGbtB5xI9ma951Zp4v3t73c31uUl27dJaHzO62EtVTdcUKAa804EtAtsvpeJWMVWKUgigm9UZcXdEwKa79fl5nLaq34lrSktAYOkexwPqYj+vbS6sn52JrluSxLE+4cEke7tYbnJ9X12SAQXKgcXY3n30+6gKf9RVqYdlNsYpEAqKVIDb6SlEkk86dP+uIg/XXb5RKEwxbDXnb6xdl9JRc+GTbeeY3/vg2h9QTdmFVblPtBhHrNenQYP/BS0n+EfUnAIBKqRmQgyhao3SiY5FMACM8higI2Lvvhpq46pDhXqsexYCz5F008C6YXGDh5gC93rJFec0pjh55DNdQu6uw3YMQ5jtf3QUXoPAoMFud3cTulMlnjC1WZG8QbqER8dzUZ4TcaQUdzribJI/mRriheBg=
|
||||
local_dir: docs/
|
||||
on:
|
||||
all_branches: true
|
||||
- provider: npm
|
||||
api_key:
|
||||
secure: Yrng6jsnMAtzrrln9DwRuY4xpcxl/WYS/1A5fckyQF6DsLmNlvqVtu3MWF+zdJgANF63G3jIee11tNBpYRecHl8FjPGwO0kc1vEgvIRVnveyR0bwIIDH6s/mR9dg4rZikflwG4XExsLyaQd7ko8aTIOIayfxJiv/u0yqwuBuBW2bFrL/41b1cKGK5+Iq2a+PdFENUPenKXISkACGaMnQF4Y/KVF98UwCiGLf957yFWc2sD6TFbjNDbAENSccvg1J3fBb+djbtzKzldl29ntp2mKVGSVASiKCRa6hSfgQulHiidFqFIKgpYJ1uATRr3UFr/NRVt1WbrgLnzY74OCX7y02c/xiYQMMEPRl/P8hHJu+KjQ3PBWsBvmsRN0QMUJv3PEjUPE3AY8sw49NoxiJZzJr7574vUBuM/dk0byYI6K/gwmUrPIhQjljzZqinS8JJJ4FjGjCdzXRhT5Q+PZvt5bF1rMOZXayNJZa+cICtmiJoU3vO2Yf6TXC3wY1veKvmcs24nws5lp4keJePTKAi1Ig7VoSxwAlgs3EGANIh7oBKx9zWnXQYMdmxbYsvLXAWcnnM+PcSCvooLmXWso3BQBeRuUUSS2oLaBpsFMsiniLNbA1cW4fwMkgUGz4oEDRi8wTD89E/1J2oNxzqtWRPawcVKpMpnBbDJHrWJPEHMw=
|
||||
email:
|
||||
secure: lomzDl71N995SzRczm7VE9OZE+PzMo4X8t3zU97agu/FMH5Qcj8BLwE+uVDTnA9Vblyj62ZsFKsNjP2Qp53Vcd+jHM4EJNWNRZYpEMIRO3LngX43r83qoFEHUvPu9s1oaa04a6FojcsJx0wl6B6Ke2AX74MXnJDLb9iZBy1mkpLUMVccVhSfhdoIzhkq3dhUw+6d8C024tNMHcgDW3VnRAsWFtiL7dCMpjLOdI+UxlkeGkQkxXXuRsZ0ZdjoSoM8NSkiYMc8x6EnekyRDoHTujX3OFxuU6+GAjrUmVzNmJWrBIqHVb0DXBQxjEaG3d/cNu5UsQyZYq5sxRRH0BaLs7F4oIQg95etasEtTtUkmsZ3pshVlsweiLU366UdbfuAf5hrJjqLrU12BKZyLjaAwyeKz031r8dA4sJtGIp5uVdXobQQTH6r958A88byJ20uaYSqhqjhZo3hkWXIQP0WQN2Ej/g57HbVNLB/nPKkMILfk/tpp7nBDLT0QrjbZxeo1dwCHqsBEV6z7ZyWyFf4xwpDsir4txL4t8ElzeGdlACjCqAJvIh5w9YzfrwijtoVMvvP6pWvn/iI640d4rsdIDe8egxgqZ1R/TMd/tdHYX+eI+ZfFmCVGj36/uXwdG7KIoIZVjRQ2tvWr9ZuydEPWPSRVGT4ycFeu6wbm3elM3I=
|
||||
skip_cleanup: true
|
||||
on:
|
||||
tags: true
|
||||
all_branches: true
|
||||
repo: parse-community/parse-server
|
||||
+1391
-45
File diff suppressed because it is too large
Load Diff
+24
-373
@@ -1,78 +1,17 @@
|
||||
# Contributing to Parse Server <!-- omit in toc -->
|
||||
# Contributing to Parse Server
|
||||
|
||||
## Table of Contents <!-- omit in toc -->
|
||||
- [Contributing](#contributing)
|
||||
- [Why Contributing?](#why-contributing)
|
||||
- [Environment Setup](#environment-setup)
|
||||
- [Recommended Tools](#recommended-tools)
|
||||
- [Setting up your local machine](#setting-up-your-local-machine)
|
||||
- [Good to Know](#good-to-know)
|
||||
- [Troubleshooting](#troubleshooting)
|
||||
- [Please Do's](#please-dos)
|
||||
- [Test against Postgres](#test-against-postgres)
|
||||
- [Postgres with Docker](#postgres-with-docker)
|
||||
- [Breaking Changes](#breaking-changes)
|
||||
- [Deprecation Policy](#deprecation-policy)
|
||||
- [Feature Considerations](#feature-considerations)
|
||||
- [Security Checks](#security-checks)
|
||||
- [Add Security Check](#add-security-check)
|
||||
- [Wording Guideline](#wording-guideline)
|
||||
- [Parse Error](#parse-error)
|
||||
- [Parse Server Configuration](#parse-server-configuration)
|
||||
- [Pull Request](#pull-request)
|
||||
- [Breaking Change](#breaking-change)
|
||||
- [Merging](#merging)
|
||||
- [Breaking Change](#breaking-change-1)
|
||||
- [Reverting](#reverting)
|
||||
- [Releasing](#releasing)
|
||||
- [General Considerations](#general-considerations)
|
||||
- [Major Release / Long-Term-Support](#major-release--long-term-support)
|
||||
- [Versioning](#versioning)
|
||||
- [Code of Conduct](#code-of-conduct)
|
||||
We really want Parse to be yours, to see it grow and thrive in the open source community.
|
||||
|
||||
## Contributing
|
||||
If you are not familiar with Pull Requests and want to know more about them, you can visit the [Creating a pull request](https://help.github.com/articles/creating-a-pull-request/) article. It contains detailed informations about the process.
|
||||
|
||||
Before you start to code, please open a [new issue](https://github.com/parse-community/parse-server/issues/new/choose) to describe your idea, or search for and continue the discussion in an [existing issue](https://github.com/parse-community/parse-server/issues).
|
||||
## Setting up the project for debugging and contributing:
|
||||
|
||||
> ⚠️ Please do not post a security vulnerability on GitHub or in the Parse Community Forum. Instead, follow the [Parse Community Security Policy](https://github.com/parse-community/parse-server/security/policy).
|
||||
### Recommended setup:
|
||||
|
||||
Please completely fill out any templates to provide essential information about your new feature or the bug you discovered.
|
||||
|
||||
Together we will plan out the best conceptual approach for your contribution, so that your and our time is invested in the best possible approach. The discussion often reveals how to leverage existing features of Parse Server to reach your goal with even less effort and in a more sustainable way.
|
||||
|
||||
When you are ready to code, you can find more information about opening a pull request in the [GitHub docs](https://help.github.com/articles/creating-a-pull-request/).
|
||||
|
||||
Whether this is your first contribution or you are already an experienced contributor, the Parse Community has your back – don't hesitate to ask for help!
|
||||
|
||||
## Why Contributing?
|
||||
|
||||
Buy cheap, buy twice. What? No, this is not the Economics 101 class, but the same is true for contributing.
|
||||
|
||||
There are two ways of writing a feature or fixing a bug. Sometimes the quick solution is to just write a Cloud Code function that does what you want. Contributing by making the change directly in Parse Server may take a bit longer, but it actually saves you much more time in the long run.
|
||||
|
||||
Consider the benefits you get:
|
||||
|
||||
- #### 🚀 Higher efficiency
|
||||
Your code is examined for efficiency and interoperability with existing features by the community.
|
||||
- #### 🛡 Stronger security
|
||||
Your code is scrutinized for bugs and vulnerabilities and automated checks help to identify security issues that may arise in the future.
|
||||
- #### 🧬 Continuous improvement
|
||||
If your feature is used by others it is likely to be continuously improved and extended by the community.
|
||||
- #### 💝 Giving back
|
||||
You give back to the community that contributed to make the Parse Platform become what it is today and for future developers to come.
|
||||
- #### 🧑🎓 Improving yourself
|
||||
You learn to better understand the inner workings of Parse Server, which will help you to write more efficient and resilient code for your own application.
|
||||
|
||||
Most importantly, with every contribution you improve your skills so that future contributions take even less time and you get all the benefits above for free — easy choice, right?
|
||||
|
||||
## Environment Setup
|
||||
|
||||
### Recommended Tools
|
||||
|
||||
* [Visual Studio Code](https://code.visualstudio.com), the popular IDE.
|
||||
* [vscode](https://code.visualstudio.com), the popular IDE.
|
||||
* [Jasmine Test Explorer](https://marketplace.visualstudio.com/items?itemName=hbenl.vscode-jasmine-test-adapter), a very practical test exploration plugin which let you run, debug and see the test results inline.
|
||||
|
||||
### Setting up your local machine
|
||||
### Setting up you local machine:
|
||||
|
||||
* [Fork](https://github.com/parse-community/parse-server) this project and clone the fork on your local machine:
|
||||
|
||||
@@ -88,23 +27,13 @@ $ npm run watch # run babel watching for local file changes
|
||||
|
||||
Once you have babel running in watch mode, you can start making changes to parse-server.
|
||||
|
||||
### Good to Know
|
||||
### Good to know:
|
||||
|
||||
* The `lib/` folder is not committed, so never make changes in there.
|
||||
* The lib/ folder is not commited, so never make changes in there.
|
||||
* Always make changes to files in the `src/` folder.
|
||||
* All the tests should point to sources in the `lib/` folder.
|
||||
* The `lib/` folder is produced by `babel` using either the `npm run build`, `npm run watch`, or the `npm run prepare` step.
|
||||
* The `npm run prepare` step is automatically invoked when your package depends on forked parse-server installed via git for example using `npm install --save git+https://github.com/[username]/parse-server#[branch/commit]`.
|
||||
* The tests are run against a single server instance. You can change the server configurations using `await reconfigureServer({ ... some configuration })` found in `spec/helper.js`.
|
||||
* The tests are ran at random.
|
||||
* Caches and Configurations are reset after every test.
|
||||
* Users are logged out after every test.
|
||||
* Cloud Code hooks are removed after every test.
|
||||
* Database is deleted after every test (indexes are not removed for speed)
|
||||
* Tests are located in the `spec` folder
|
||||
* For better test reporting enable `PARSE_SERVER_LOG_LEVEL=debug`
|
||||
|
||||
### Troubleshooting
|
||||
### Troubleshooting:
|
||||
|
||||
*Question*: I modify the code in the src folder but it doesn't seem to have any effect.<br/>
|
||||
*Answer*: Check that `npm run watch` is running
|
||||
@@ -113,319 +42,41 @@ Once you have babel running in watch mode, you can start making changes to parse
|
||||
*Answer*: The easiest way is to install [Jasmine Test Explorer](https://marketplace.visualstudio.com/items?itemName=hbenl.vscode-test-explorer), it will let you run selectively tests and debug them.
|
||||
|
||||
*Question*: How do I deploy my forked version on my servers?<br/>
|
||||
*Answer*: In your `package.json`, update the `parse-server` dependency to `https://github.com/[username]/parse-server#[branch/commit]`. Run `npm install`, commit the changes and deploy to your servers.
|
||||
|
||||
*Question*: How do I deploy my forked version using docker?<br/>
|
||||
*Answer*: In your `package.json`, update the `parse-server` dependency to `https://github.com/[username]/parse-server#[branch/commit]`. Make sure the `npm install` step in your `Dockerfile` is running under non-privileged user for the ``npm run prepare`` step to work correctly. For official node images from hub.docker.com that non-privileged user is `node` with `/home/node` working directory.
|
||||
*Answer*: In your `package.json`, update the `parse-server` dependency to `https://github.com/MY_USERNAME/parse-server#MY_FEATURE`. Run `npm install`, commit the changes and deploy to your servers.
|
||||
|
||||
|
||||
### Please Do's
|
||||
|
||||
* Begin by reading the [Development Guide](http://docs.parseplatform.org/parse-server/guide/#development-guide) to learn how to get started running the parse-server.
|
||||
* Take testing seriously! Aim to increase the test coverage with every pull request. To obtain the test coverage of the project, run: `npm run coverage`
|
||||
* Run the tests for the file you are working on with the following command: `npm test spec/MyFile.spec.js`
|
||||
* Take testing seriously! Aim to increase the test coverage with every pull request. To obtain the test coverage of the project, run:
|
||||
* **Windows**: `npm run coverage:win`
|
||||
* **Unix**: `npm run coverage`
|
||||
* Run the tests for the file you are working on with the following command:
|
||||
* **Windows**: `npm run test:win spec/MyFile.spec.js`
|
||||
* **Unix**: `npm test spec/MyFile.spec.js`
|
||||
* Run the tests for the whole project to make sure the code passes all tests. This can be done by running the test command for a single file but removing the test file argument. The results can be seen at *<PROJECT_ROOT>/coverage/lcov-report/index.html*.
|
||||
* Lint your code by running `npm run lint` to make sure the code is not going to be rejected by the CI.
|
||||
* **Do not** publish the *lib* folder.
|
||||
* Mocks belong in the `spec/support` folder.
|
||||
* Please consider if any changes to the [docs](http://docs.parseplatform.org) are needed or add additional sections in the case of an enhancement or feature.
|
||||
|
||||
### Test against Postgres
|
||||
### Run your tests against Postgres (optional)
|
||||
|
||||
If your pull request introduces a change that may affect the storage or retrieval of objects, you may want to make sure it plays nice with Postgres.
|
||||
|
||||
* Run the tests against the postgres database with `PARSE_SERVER_TEST_DB=postgres PARSE_SERVER_TEST_DATABASE_URI=postgres://postgres:password@localhost:5432/parse_server_postgres_adapter_test_database npm run testonly`. You'll need to have postgres running on your machine and setup [appropriately](https://github.com/parse-community/parse-server/blob/master/scripts/before_script_postgres.sh) or use [`Docker`](#run-a-parse-postgres-with-docker).
|
||||
* Run the tests against the postgres database with `PARSE_SERVER_TEST_DB=postgres npm test`. You'll need to have postgres running on your machine and setup [appropriately](https://github.com/parse-community/parse-server/blob/master/.travis.yml#L37)
|
||||
* The Postgres adapter has a special debugger that traces all the sql commands. You can enable it with setting the environment variable `PARSE_SERVER_LOG_LEVEL=debug`
|
||||
* If your feature is intended to only work with MongoDB, you should disable PostgreSQL-specific tests with:
|
||||
|
||||
|
||||
- `describe_only_db('mongo')` // will create a `describe` that runs only on mongoDB
|
||||
- `it_only_db('mongo')` // will make a test that only runs on mongo
|
||||
- `it_exclude_dbs(['postgres'])` // will make a test that runs against all DB's but postgres
|
||||
* Similarly, if your feature is intended to only work with PostgreSQL, you should disable MongoDB-specific tests with:
|
||||
|
||||
- `describe_only_db('postgres')` // will create a `describe` that runs only on postgres
|
||||
- `it_only_db('postgres')` // will make a test that only runs on postgres
|
||||
- `it_exclude_dbs(['mongo'])` // will make a test that runs against all DB's but mongo
|
||||
|
||||
* If your feature is intended to work with MongoDB and PostgreSQL, you can include or exclude tests more granularly with:
|
||||
### Generate Parse Server Config Definition
|
||||
|
||||
- `it_only_mongodb_version('>=4.4')` // will test with any version of Postgres but only with version >=4.4 of MongoDB; accepts semver notation to specify a version range
|
||||
- `it_exclude_mongodb_version('<4.4')` // will test with any version of Postgres and MongoDB, excluding version <4.4 of MongoDB; accepts semver notation to specify a version range
|
||||
- `it_only_postgres_version('>=13')` // will test with any version of Mongo but only with version >=13 of Postgres; accepts semver notation to specify a version range
|
||||
- `it_exclude_postgres_version('<13')` // will test with any version of Postgres and MongoDB, excluding version <13 of Postgres; accepts semver notation to specify a version range
|
||||
If you want to make changes to [Parse Server Configuration][config] add the desired configuration to [src/Options/index.js][config-index] and run `npm run definitions`. This will output [src/Options/Definitions.js][config-def] and [src/Options/docs.js][config-docs].
|
||||
|
||||
#### Postgres with Docker
|
||||
To view docs run `npm run docs` and check the `/out` directory.
|
||||
|
||||
[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.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:
|
||||
|
||||
```
|
||||
docker stop parse-postgres
|
||||
```
|
||||
|
||||
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
|
||||
COPY ./scripts/setup-dbs.sh /docker-entrypoint-initdb.d/setup-dbs.sh
|
||||
RUN chmod +x /docker-entrypoint-initdb.d/setup-dbs.sh
|
||||
```
|
||||
|
||||
Note that the script above will ONLY be executed during initialization of the container with no data in the database, see the official [Postgres image](https://hub.docker.com/_/postgres) for details. If you want to use the script to run again be sure there is no data in the /var/lib/postgresql/data of the container.
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
Breaking changes should be avoided whenever possible. For a breaking change to be accepted, the benefits of the change have to clearly outweigh the costs of developers having to adapt their deployments. If a breaking change is only cosmetic it will likely be rejected and preferred to become obsolete organically during the course of further development, unless it is required as part of a larger change. Breaking changes should follow the [Deprecation Policy](#deprecation-policy).
|
||||
|
||||
Please consider that Parse Server is just one component in a stack that requires attention. A breaking change requires resources and effort to adapt an environment. An unnecessarily high frequency of breaking changes can have detrimental side effects such as:
|
||||
- "upgrade fatigue" where developers run old versions of Parse Server because they cannot always attend to every update that contains a breaking change
|
||||
- less secure Parse Server deployments that run on old versions which is contrary to the security evangelism Parse Server intends to facilitate for developers
|
||||
- less feedback and slower identification of bugs and an overall slow-down of Parse Server development because new versions with breaking changes also include new features we want to get feedback on
|
||||
|
||||
### Deprecation Policy
|
||||
|
||||
If you change or remove an existing feature that would lead to a breaking change, use the following deprecation pattern:
|
||||
- Make the new feature or change optional, if necessary with a new Parse Server option parameter.
|
||||
- Use a default value that falls back to existing behavior.
|
||||
- Add a deprecation definition in `Deprecator/Deprecations.js` that will output a deprecation warning log message on Parse Server launch, for example:
|
||||
> DeprecationWarning: The Parse Server option 'example' will be removed in a future release.
|
||||
|
||||
For deprecations that can only be determined ad-hoc during runtime, for example Parse Query syntax deprecations, use the `Deprecator.logRuntimeDeprecation()` method.
|
||||
|
||||
Deprecations become breaking changes after notifying developers through deprecation warnings for at least one entire previous major release. For example:
|
||||
- `4.5.0` is the current version
|
||||
- `4.6.0` adds a new optional feature and a deprecation warning for the existing feature
|
||||
- `5.0.0` marks the beginning of logging the deprecation warning for one entire major release
|
||||
- `6.0.0` makes the breaking change by removing the deprecation warning and making the new feature replace the existing feature
|
||||
|
||||
See the [Deprecation Plan](https://github.com/parse-community/parse-server/blob/master/DEPRECATIONS.md) for an overview of deprecations and planned breaking changes.
|
||||
|
||||
## Feature Considerations
|
||||
### Security Checks
|
||||
|
||||
The Parse Server security checks feature warns developers about weak security settings in their Parse Server deployment.
|
||||
|
||||
A security check needs to be added for every new feature or enhancement that allows the developer to configure it in a way that weakens security mechanisms or exposes functionality which creates a weak spot for malicious attacks. If you are not sure whether your feature or enhancements requires a security check, feel free to ask.
|
||||
|
||||
For example, allowing public read and write to a class may be useful to simplify development but should be disallowed in a production environment.
|
||||
|
||||
Security checks are added in [CheckGroups](https://github.com/parse-community/parse-server/tree/master/src/Security/CheckGroups).
|
||||
|
||||
#### Add Security Check
|
||||
Adding a new security check for your feature is easy and fast:
|
||||
1. Look into [CheckGroups](https://github.com/parse-community/parse-server/tree/master/src/Security/CheckGroups) whether there is an existing `CheckGroup[Category].js` file for the category of check to add. For example, a check regarding the database connection is added to `CheckGroupDatabase.js`.
|
||||
2. If you did not find a file, duplicate an existing file and replace the category name in `setName()` and the checks in `setChecks()`:
|
||||
```js
|
||||
class CheckGroupNewCategory extends CheckGroup {
|
||||
setName() {
|
||||
return 'House';
|
||||
}
|
||||
setChecks() {
|
||||
return [
|
||||
new Check({
|
||||
title: 'Door locked',
|
||||
warning: 'Anyone can enter your house.',
|
||||
solution: 'Lock the door.',
|
||||
check: () => {
|
||||
return; // Example of a passing check
|
||||
}
|
||||
}),
|
||||
new Check({
|
||||
title: 'Camera online',
|
||||
warning: 'Security camera is offline.',
|
||||
solution: 'Check the camera.',
|
||||
check: async () => {
|
||||
throw 1; // Example of a failing check
|
||||
}
|
||||
}),
|
||||
];
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
3. If you added a new file in the previous step, reference the file in [CheckGroups.js](https://github.com/parse-community/parse-server/blob/master/src/Security/CheckGroups/CheckGroups.js), which is the collector of all security checks:
|
||||
```
|
||||
export { default as CheckGroupNewCategory } from './CheckGroupNewCategory';
|
||||
```
|
||||
4. Add a test that covers the new check to [SecurityCheckGroups.js](https://github.com/parse-community/parse-server/blob/master/spec/SecurityCheckGroups.js) for the cases of success and failure.
|
||||
|
||||
#### Wording Guideline
|
||||
Consider the following when adding a new security check:
|
||||
- *Group.name*: The category name; ends without period as this is a headline.
|
||||
- *Check.title*: Is the positive hypothesis that should be checked, for example "Door locked" instead of "Door unlocked"; ends without period as this is a title.
|
||||
- *Check.warning*: The warning if the test fails; ends with period as this is a description.
|
||||
- *Check.solution*: The recommended solution if the test fails; ends with period as this is an instruction.
|
||||
- The wordings must not contain any sensitive information such as keys, as the security report may be exposed in logs.
|
||||
- The wordings should be concise and not contain verbose explanations, for example "Door locked" instead of "Door has been locked securely".
|
||||
- Do not use pronouns such as "you" or "your" because log files can have various readers with different roles. Do not use pronouns such as "I" or "me" because although we love it dearly, Parse Server is not a human.
|
||||
|
||||
### Parse Error
|
||||
|
||||
Introducing new Parse Errors requires the following steps:
|
||||
|
||||
1. Research whether an existing Parse Error already covers the error scenario. Keep in mind that reusing an already existing Parse Error does not allow to distinguish between scenarios in which the same error is thrown, so it may be necessary to add a new and more specific Parse Error, even though a more general Parse Error already exists.
|
||||
⚠️ Currently (as of Dec. 2020), there are inconsistencies between the Parse Errors documented in the Parse Guides, coded in the Parse JS SDK and coded in Parse Server, therefore research regarding the availability of error codes has to be conducted in all of these sources.
|
||||
1. Add the new Parse Error to [/src/ParseError.js](https://github.com/parse-community/Parse-SDK-JS/blob/master/src/ParseError.js) in the Parse JavaScript SDK. This is the primary reference for Parse Errors for the Parse JavaScript SDK and Parse Server.
|
||||
1. Create a pull request for the Parse JavaScript SDK including the new Parse Errors. The PR needs to be merged and a new Parse JS SDK version needs to be released.
|
||||
1. Change the Parse JS SDK dependency in [package.json](https://github.com/parse-community/parse-server/blob/master/package.json) of Parse Server to the newly released Parse JS SDK version, so that the new Parse Error is recognized by Parse Server.
|
||||
1. When throwing the new Parse Error in code, do not hard-code the error code but instead reference the error code from the Parse Error. For example:
|
||||
```javascript
|
||||
throw new Parse.Error(Parse.Error.EXAMPLE_ERROR_CODE, 'Example error message.');
|
||||
```
|
||||
1. Choose a descriptive error message that provdes more details about the specific error scenario. Different error messages may be used for the same error code. For example:
|
||||
```javascript
|
||||
throw new Parse.Error(Parse.Error.FILE_SAVE_ERROR, 'The file could not be saved because it exceeded the maximum allowed file size.');
|
||||
throw new Parse.Error(Parse.Error.FILE_SAVE_ERROR, 'The file could not be saved because the file format was incorrect.');
|
||||
```
|
||||
1. Add the new Parse Error to the [docs](https://github.com/parse-community/docs/blob/gh-pages/_includes/common/errors.md).
|
||||
|
||||
### Parse Server Configuration
|
||||
|
||||
Introducing new [Parse Server configuration][config] parameters requires the following steps:
|
||||
|
||||
1. Add parameters definitions in [/src/Options/index.js][config-index].
|
||||
2. If the new parameter does not have one single value but is a parameter group (an object containing multiple sub-parameters):
|
||||
- add the environment variable prefix for the parameter group to `nestedOptionEnvPrefix` in [/resources/buildConfigDefinition.js](https://github.com/parse-community/parse-server/blob/master/resources/buildConfigDefinition.js)
|
||||
- add the parameter group type to `nestedOptionTypes` in [/resources/buildConfigDefinition.js](https://github.com/parse-community/parse-server/blob/master/resources/buildConfigDefinition.js)
|
||||
|
||||
For example, take a look at the existing Parse Server `security` parameter. It is a parameter group, because it has multiple sub-parameter such as `checkGroups`. Its interface is defined in [index.js][config-index] as `export interface SecurityOptions`. Therefore, the value to add to `nestedOptionTypes` would be `SecurityOptions`, the value to add to `nestedOptionEnvPrefix` would be `PARSE_SERVER_SECURITY_`.
|
||||
|
||||
3. Execute `npm run definitions` to automatically create the definitions in [/src/Options/Definitions.js][config-def] and [/src/Options/docs.js][config-docs].
|
||||
4. Add parameter value validation in [/src/Config.js](https://github.com/parse-community/parse-server/blob/master/src/Config.js).
|
||||
5. Add test cases to ensure the correct parameter value validation. Parse Server throws an error at launch if an invalid value is set for any configuration parameter.
|
||||
6. Execute `npm run docs` to generate the documentation in the `/out` directory. Take a look at the documentation whether the description and formatting of the newly introduced parameters is satisfactory.
|
||||
|
||||
## Pull Request
|
||||
|
||||
For release automation, the title of pull requests needs to be written in a defined syntax. We loosely follow the [Conventional Commits](https://www.conventionalcommits.org) specification, which defines this syntax:
|
||||
|
||||
```
|
||||
<type>: <summary>
|
||||
```
|
||||
|
||||
The _type_ is the category of change that is made, possible types are:
|
||||
- `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
|
||||
- `style` - edit code style
|
||||
- `build` - retry failing build and anything build process related
|
||||
- `perf` - performance optimization
|
||||
- `ci` - continuous integration
|
||||
- `test` - tests
|
||||
|
||||
The _summary_ is a short change description in present tense, not capitalized, without period at the end. This summary will also be used as the changelog entry.
|
||||
- It must be short and self-explanatory for a reader who does not see the details of the full pull request description
|
||||
- It must not contain abbreviations, e.g. instead of `LQ` write `LiveQuery`
|
||||
- It must use the correct product and feature names as referenced in the documentation, e.g. instead of `Cloud Validator` use `Cloud Function validation`
|
||||
- In case of a breaking change, the summary must not contain duplicate information that is also in the [BREAKING CHANGE](#breaking-change) chapter of the pull request description. It must not contain a note that it is a breaking change, as this will be automatically flagged as such if the pull request description contains the BREAKING CHANGE chapter.
|
||||
|
||||
For example:
|
||||
|
||||
```
|
||||
feat: add handle to door for easy opening
|
||||
```
|
||||
|
||||
Currently, we are not making use of the commit _scope_, which would be written as `<type>(<scope>): <summary>`, that attributes a change to a specific part of the product.
|
||||
|
||||
### Breaking Change
|
||||
|
||||
If a pull request contains a braking change, the description of the pull request must contain a dedicated chapter at the bottom to indicate this. This is to assist the committer of the pull request to avoid merging a breaking change as non-breaking.
|
||||
|
||||
## Merging
|
||||
|
||||
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.
|
||||
|
||||
- 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
|
||||
|
||||
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.
|
||||
|
||||
### 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 <hash>.`, where the hash is the SHA of the commit being reverted. For example:
|
||||
|
||||
```
|
||||
revert: fix: remove handle from door
|
||||
|
||||
This reverts commit 1234567890abcdef.
|
||||
```
|
||||
|
||||
⚠️ A `revert` prefix will *always* trigger a release. Generally, a commit that did not trigger a release when it was initially merged should also not trigger a release when it is reverted. For example, do not use the `revert` prefix when reverting a commit that has a `ci` prefix:
|
||||
|
||||
```
|
||||
ci: add something
|
||||
```
|
||||
is reverted with:
|
||||
```
|
||||
ci: remove something
|
||||
```
|
||||
instead of:
|
||||
```
|
||||
revert: ci: add something
|
||||
|
||||
This reverts commit 1234567890abcdef.
|
||||
```
|
||||
|
||||
## Releasing
|
||||
|
||||
### General Considerations
|
||||
|
||||
- The `package-lock.json` file has to be deleted and recreated by npm from scratch in regular intervals using the `npm i` command. It is not enough to only update the file via automated security pull requests (e.g. dependabot, snyk), that can create inconsistencies between sub-devependencies of a dependency and increase the chances of vulnerabilities. The file should be recreated once every release cycle which is usually monthly.
|
||||
|
||||
### 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
|
||||
|
||||
> The following versioning system is applied since Parse Server 5.0.0 and does not necessarily apply to previous releases.
|
||||
|
||||
Parse Server follows [semantic versioning](https://semver.org) with a flavor of [calendric versioning](https://calver.org). Semantic versioning makes Parse Server easy to upgrade because breaking changes only occur in major releases. Calendric versioning gives an additional sense of how old a Parse Server release is and allows for Long-Term Support of previous major releases.
|
||||
|
||||
Example version: `5.0.0-alpha.1`
|
||||
|
||||
Syntax: `[major]`**.**`[minor]`**.**`[patch]`**-**`[pre-release-label]`**.**`[pre-release-increment]`
|
||||
|
||||
- The `major` version increments with the first release of every year and may include changes that are *not backwards compatible*.
|
||||
- The `minor` version increments during the year and may include new features or improvements of existing features that are backwards compatible.
|
||||
- The `patch` version increments during the year and may include bug fixes that are backwards compatible.
|
||||
- The `pre-release-label` is optional for pre-release versions such as:
|
||||
- `-alpha` (likely to contain bugs, likely to change in features until release)
|
||||
- `-beta` (likely to contain bugs, no change in features until release)
|
||||
- The `[pre-release-increment]` is a number that increments with every new version of a pre-release
|
||||
|
||||
Exceptions:
|
||||
- The `major` version may increment during the year in the unlikely event that a breaking change is so urgent that it cannot wait for the next yearly release. An example would be a vulnerability fix that leads to an unavoidable breaking change. However, security requirements depend on the application and not every vulnerability may affect every deployment, depending on the features used. Therefore we usually prefer to deprecate insecure functionality and introduce the breaking change following our [deprecation policy](#deprecation-policy).
|
||||
|
||||
## Code of Conduct
|
||||
### Code of Conduct
|
||||
|
||||
This project adheres to the [Contributor Covenant Code of Conduct](https://github.com/parse-community/parse-server/blob/master/CODE_OF_CONDUCT.md). By participating, you are expected to honor this code.
|
||||
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
# Deprecation Plan <!-- omit in toc -->
|
||||
|
||||
The following is a list of deprecations, according to the [Deprecation Policy](https://github.com/parse-community/parse-server/blob/master/CONTRIBUTING.md#deprecation-policy). After a feature becomes deprecated, and giving developers time to adapt to the change, the deprecated feature will eventually be removed, leading to a breaking change. Developer feedback during the deprecation period may postpone or even revoke the introduction of the breaking change.
|
||||
|
||||
| ID | Change | Issue | Deprecation [ℹ️][i_deprecation] | Planned Removal [ℹ️][i_removal] | Status [ℹ️][i_status] | Notes |
|
||||
|--------|-------------------------------------------------|----------------------------------------------------------------------|---------------------------------|---------------------------------|-----------------------|-------|
|
||||
| DEPPS1 | Native MongoDB syntax in aggregation pipeline | [#7338](https://github.com/parse-community/parse-server/issues/7338) | 5.0.0 (2022) | 6.0.0 (2023) | deprecated | - |
|
||||
| DEPPS2 | Config option `directAccess` defaults to `true` | [#6636](https://github.com/parse-community/parse-server/pull/6636) | 5.0.0 (2022) | 6.0.0 (2023) | deprecated | - |
|
||||
| DEPPS3 | Config option `enforcePrivateUsers` defaults to `true` | [#7319](https://github.com/parse-community/parse-server/pull/7319) | 5.0.0 (2022) | 6.0.0 (2023) | deprecated | - |
|
||||
| DEPPS4 | Remove convenience method for http request `Parse.Cloud.httpRequest` | [#7589](https://github.com/parse-community/parse-server/pull/7589) | 5.0.0 (2022) | 6.0.0 (2023) | deprecated | - |
|
||||
| DEPPS5 | Config option `allowClientClassCreation` defaults to `false` | [#7925](https://github.com/parse-community/parse-server/pull/7925) | 5.3.0 (2023) | 7.0.0 (2024) | deprecated | - |
|
||||
|
||||
[i_deprecation]: ## "The version and date of the deprecation."
|
||||
[i_removal]: ## "The version and date of the planned removal."
|
||||
[i_status]: ## "The current status of the deprecation: deprecated (the feature is deprecated and still available), removed (the deprecated feature has been removed and is unavailable), retracted (the deprecation has been retracted and the feature will not be removed."
|
||||
+1
-23
@@ -1,43 +1,21 @@
|
||||
############################################################
|
||||
# Build stage
|
||||
############################################################
|
||||
FROM node:lts-alpine as build
|
||||
|
||||
RUN apk update; \
|
||||
apk add git;
|
||||
WORKDIR /tmp
|
||||
|
||||
# Copy package.json first to benefit from layer caching
|
||||
COPY package*.json ./
|
||||
|
||||
# Copy src to have config files for install
|
||||
COPY . .
|
||||
|
||||
# Clean npm cache; added to fix an issue with the install process
|
||||
RUN npm cache clean --force
|
||||
|
||||
# Install all dependencies
|
||||
RUN npm ci
|
||||
|
||||
# Run build steps
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
|
||||
############################################################
|
||||
# Release stage
|
||||
############################################################
|
||||
FROM node:lts-alpine as release
|
||||
|
||||
RUN apk update; \
|
||||
apk add git;
|
||||
|
||||
VOLUME /parse-server/cloud /parse-server/config
|
||||
|
||||
WORKDIR /parse-server
|
||||
|
||||
COPY package*.json ./
|
||||
|
||||
# Clean npm cache; added to fix an issue with the install process
|
||||
RUN npm cache clean --force
|
||||
RUN npm ci --production --ignore-scripts
|
||||
|
||||
COPY bin bin
|
||||
|
||||
+58
-2
@@ -2,7 +2,7 @@
|
||||
If you believe you have found a security vulnerability on one of parse-community maintained packages,
|
||||
we encourage you to let us know right away.
|
||||
We will investigate all legitimate reports and do our best to quickly fix the problem.
|
||||
Before making a report, please review this page to understand our disclosure policy and how to communicate with us.
|
||||
Before reporting though, please review this page including and those things that should not be reported.
|
||||
|
||||
# Responsible Disclosure Policy
|
||||
If you comply with the policies below when reporting a security issue to parse community,
|
||||
@@ -16,4 +16,60 @@ We ask that:
|
||||
|
||||
# Communicating with us
|
||||
|
||||
All vulnerabilities should be privately reported to us by going to [https://report.parseplatform.org](https://report.parseplatform.org). Alternatively, you can send an email to [security@parseplatform.org](mailto:security@parseplatform.org).
|
||||
All vulnerability should be privately reported to either [Node Security](https://nodesecurity.io/report) or directly to us at the following address [security at parseplatform dot org](mailto:security@parseplatform.org)
|
||||
|
||||
You can use our PGP public key, which is also uploaded [here](hkp://pgp.mit.edu):
|
||||
|
||||
```
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
mQINBFoi200BEACnFHa4Atvw62TjpI5uDtyOF1Ab6gd6898ITXlzFVlAoiqTBE2o
|
||||
S3H9vCe6w32HkTqyKiymdk50mAaDJrEOyAZSqj0gc4r7vmCx2s7f3iO9A9PEGsHj
|
||||
UROnkJ5v2su1Dk95XQrbrR4JyNvFMLLqEbdK78Mhx/Xd5QqOD8pop0cS8pF1f1Mb
|
||||
3MiZb3bxFj+7n+KC80C2+CNyJt95alVnq2MuwbEuwTJQV5CEgRqzBCov9qnLgloK
|
||||
w7YP4YLkKZoMZQ45mWCUTmn8YIa9PabDLXUhlKv3MQInhnJIELb+jSKO96glHr6p
|
||||
DpFf0pwRfsuoIhy3jaO7K/ws5uZY3/Ae3gjrAlOB8jhae0POWSwEM+iWHg3wcjpt
|
||||
lRdu/OgPXqKIgMAXw6Kx9XrskEhOI9ZQfHZlK3HL4ArSdtGYIO5pVNeRssljJvJm
|
||||
G5HJuGBaPCQNYX7BWJkXFF0HYV6Ke2JDXGVSM4ubPJsZcA4Yx7SYyvuOsNgqHOFM
|
||||
9snfPfAPAhu/4zCYNCO2NNBc7HaH+qwIvveWX5tTGe6UpX/wOcD8xkoEn+UygQbO
|
||||
lwu+kjFn8H6RlDChPR65aJTU5Lu0kqRnej1gCjYzOS6AjFOjLuRBlRaey+myhQHO
|
||||
TbII9nkYI3abYhD8Di77Ve6XFMQI1grPGaqNp6ZLfej1u8PExpclzxTbtQARAQAB
|
||||
tDdQYXJzZVBsYXRmb3JtLm9yZyBTZWN1cml0eSA8c2VjdXJpdHlAcGFyc2VwbGF0
|
||||
Zm9ybS5vcmc+iQI9BBMBCgAnBQJaIttNAhsDBQkHhh+ABQsJCAcDBRUKCQgLBRYC
|
||||
AwEAAh4BAheAAAoJEOaNxtHMZ3/L13EP/2X4i0h1Jidr0GN6t7LUJFDBDHxnY+V+
|
||||
kPXuucWqtFSkiznHNWSKh8N1LY9N+5eYQj21fJw2RV+ePZKb07jcThG8G1qg+tIn
|
||||
bbLO2RxmuO/ISgqgvpmtZH9FHtjpGaGStCQqljtZDcBI0Y52l9SLItZjoiRlXePb
|
||||
C+embDhO6Wzgsi+zkzskm8ISw4mTbDY+HN/1TakCsfMMs8J7twQar+eW97WibzRW
|
||||
pvhIeJ6egzxs2dW0bXxb0OPvx9xm/FxxRgf+2vySYFnZ88j7Luur4VKdBvh1NtUT
|
||||
n+xLp/heCHQ/If1ou1Dd6rypEng3A75WANf5m1TAd8JrlQZEOLm4oW+brpDBamqc
|
||||
dP2z2/zzKykZzRoN1PSX9JRsyLhJhZSXIm/49gATtkaUFBBsFrrwqdWTV7tWFmQI
|
||||
Oe2wT9IExe+RmXsVqwIndyVkcROuXXGt3y92dcmEriNUGmYXJchs9pAdOCkpHqmf
|
||||
BkZWalXuWdWiwshNiEF3KWiNbw6BMEWlKPuuJ6kWevLj35MQMLIIlGLx722/kbBB
|
||||
KIJfp8NdWncIyx0ulq70GWdNaKvu+9dDElMCsPqXeVxbXVpGiBcIgX1S0JEU0YX7
|
||||
kShAAkZDwhyfUh4IYbyJbNVNzhMazDqI48kP8Kz8vqvZAf3SXyjSBL1XKWLAfKtb
|
||||
Bs4OuHR3FDkjuQINBFoi200BEADoh8xHW4SVuVnF+Y82Z2B25Ybhwd13mP3LUxwX
|
||||
cRYeyVge8V7kyftYiUbIybDoxa3H8ysiSgH51yHkoRsq8JO3WEnJZop1ZnjOKhjv
|
||||
g73vciKImAGrsTVtUR4ErwUsHaXgCFmlmyTI/RLPDfvHdZyYm9wji4XLZrFu5O/s
|
||||
JD1wU1/aMYCBRjrszI4uklyqGUx2kKLYEI7iITAVVQ8/xFlPPYhVX02jR7fCqTkI
|
||||
ualVAzK/6Lc4VGqjzFG5kElLCIVDTMLJvPETgXeSAuBigcC+osTw8IRhqY87qNbn
|
||||
ZpGDKJxV7ARXg5akg/98xI1TZiAMP1vHVKke7XiH5E6M/3IyPiY5ns4SaXu3ZonN
|
||||
EUR9Z60IeUHVkGpMUk0K0avkaLIAWb93D6jlZ+fJoNBuCoWB31d6+/W8LnrocTQb
|
||||
mL9ls8HiabrPDsuzI9y3ptOjgGn8pmWoJHM2fiQ5eElexHFB+1QTqq1yiTnuohi/
|
||||
p+BbKK6V/FZbrP4Rd3hOmPkOWag3KvwGFF8vW6gmx3lft+B90TtBSMv46lqDI5TX
|
||||
VUr3Un9YHIF+xQMNAgZuJkTIVLrYgexRHnJGrwhRXSZgpVkvj/ygIhR2EvY8zySh
|
||||
/EFw9ZTmhOrR34q/qlChEuUriP1cc1ygTnjfcw+ZgsTPh50VZk8s5cZAa8tVFINR
|
||||
cZKBSwARAQABiQIlBBgBCgAPBQJaIttNAhsMBQkHhh+AAAoJEOaNxtHMZ3/LgbUQ
|
||||
AJHyhtH3bkQsWH0Z+PonbcNxZNq1ZlOfosQIWRzX8BioJpQ5qttkKt5PwhBDrr9h
|
||||
4ySYzsbTg3fSkIkjfUfaRr+62xay5efaIwG5PcHdXOR/fKCJ6NrTvMEzPUZ8521e
|
||||
yk0rjm++bNqh+Knez0+xnk99n1XlPrMAXrEb63oM5IBnpJC1PcztzMhdvsNw+B/l
|
||||
2YcVhXH2peOp/GbAd58fB7JMggwumCktFnXYsYZlMFch3mwKDb66Qi2gbCgK2KHU
|
||||
sj/mPag7vpy1E4lOlBnDeZpYnjfE/8VJkT4ck5OAwywZK/NUqLfh35RaIwjeXDLM
|
||||
nlLff3HQJPXYzavCzLZ5dMZJfn0968NIHitjoW9VLs9UhrY7EWI7T6GAMX3wHcq6
|
||||
ssGOkS0Y1OW8s7jFuoe00PByjiCHcFjBG2NF3n08Nu2c5hewGPs0FdhTadQtHpI2
|
||||
TTeSIxQ2Ui21UfcX4wMbqELh871ZeQcbVp5LWWibVbfy4mx4Tq/Hvgp7DeBh8DLF
|
||||
/7MDDwZ+RIBoy98CYz4xsFMdS/9L64uBk/0C+U4OwFJI1FDDxFp6cDqjxdykWi48
|
||||
wsCczfashguiuJeJ1Ug8URRLY/DKQmjSJaCwy3McK/MOb+JVMazMUyrU9XaGuP4Y
|
||||
Co6fHPyjrvmE5DtU5Vp8O68ZpOYrkM6X22dIQpPi6atm
|
||||
=o7Nx
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
```
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
#!/bin/bash
|
||||
#!/bin/sh
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
NC='\033[0m'
|
||||
@@ -163,7 +163,7 @@ cat > ./package.json << EOF
|
||||
"start": "parse-server config.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"parse-server": "^3.9.0"
|
||||
"parse-server": "^2.0.0"
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
@@ -1,381 +0,0 @@
|
||||
# [5.3.0-alpha.9](https://github.com/parse-community/parse-server/compare/5.3.0-alpha.8...5.3.0-alpha.9) (2022-05-07)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* depreciate allowClientClassCreation defaulting to true ([#7925](https://github.com/parse-community/parse-server/issues/7925)) ([38ed96a](https://github.com/parse-community/parse-server/commit/38ed96ace534d639db007aa7dd5387b2da8f03ae))
|
||||
|
||||
# [5.3.0-alpha.8](https://github.com/parse-community/parse-server/compare/5.3.0-alpha.7...5.3.0-alpha.8) (2022-05-06)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add support for Node 17 and 18 ([#7896](https://github.com/parse-community/parse-server/issues/7896)) ([3e9f292](https://github.com/parse-community/parse-server/commit/3e9f292d840334244934cee9a34545ac86313549))
|
||||
|
||||
# [5.3.0-alpha.7](https://github.com/parse-community/parse-server/compare/5.3.0-alpha.6...5.3.0-alpha.7) (2022-04-25)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* security upgrade @parse/fs-files-adapter from 1.2.1 to 1.2.2 ([#7948](https://github.com/parse-community/parse-server/issues/7948)) ([20fc4e2](https://github.com/parse-community/parse-server/commit/20fc4e23b53c91aac657f894bd70d049b7525c37))
|
||||
|
||||
# [5.3.0-alpha.6](https://github.com/parse-community/parse-server/compare/5.3.0-alpha.5...5.3.0-alpha.6) (2022-04-11)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* peer dependency mismatch for GraphQL dependencies ([#7934](https://github.com/parse-community/parse-server/issues/7934)) ([b7a1d76](https://github.com/parse-community/parse-server/commit/b7a1d7617b4bcac677cecedfeb6ac4a27447083b))
|
||||
|
||||
# [5.3.0-alpha.5](https://github.com/parse-community/parse-server/compare/5.3.0-alpha.4...5.3.0-alpha.5) (2022-04-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* security upgrade moment from 2.29.1 to 2.29.2 ([#7931](https://github.com/parse-community/parse-server/issues/7931)) ([6b68593](https://github.com/parse-community/parse-server/commit/6b68593eaec17e8b183899d2b92699c9ede7625b))
|
||||
|
||||
# [5.3.0-alpha.4](https://github.com/parse-community/parse-server/compare/5.3.0-alpha.3...5.3.0-alpha.4) (2022-04-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* custom database options are not passed to MongoDB GridFS ([#7911](https://github.com/parse-community/parse-server/issues/7911)) ([a72b384](https://github.com/parse-community/parse-server/commit/a72b384f76137a3d83ffb69f65cb25aff1bbab4f))
|
||||
|
||||
# [5.3.0-alpha.3](https://github.com/parse-community/parse-server/compare/5.3.0-alpha.2...5.3.0-alpha.3) (2022-03-27)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add MongoDB 5.2 support ([#7894](https://github.com/parse-community/parse-server/issues/7894)) ([6b4b358](https://github.com/parse-community/parse-server/commit/6b4b358f0842ae920e45652f5e8b2afebc6caf3a))
|
||||
|
||||
# [5.3.0-alpha.2](https://github.com/parse-community/parse-server/compare/5.3.0-alpha.1...5.3.0-alpha.2) (2022-03-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* security upgrade parse push adapter from 4.1.0 to 4.1.2 ([#7893](https://github.com/parse-community/parse-server/issues/7893)) ([ef56e98](https://github.com/parse-community/parse-server/commit/ef56e98ef65041b4d3b7b82cce3473269c27f6fd))
|
||||
|
||||
# [5.3.0-alpha.1](https://github.com/parse-community/parse-server/compare/5.2.1-alpha.2...5.3.0-alpha.1) (2022-03-27)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add MongoDB 5.1 compatibility ([#7682](https://github.com/parse-community/parse-server/issues/7682)) ([90155cf](https://github.com/parse-community/parse-server/commit/90155cf1680e5e0499b0000e071c6cb0ce3aef96))
|
||||
|
||||
## [5.2.1-alpha.2](https://github.com/parse-community/parse-server/compare/5.2.1-alpha.1...5.2.1-alpha.2) (2022-03-26)
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* reduce database operations when using the constant parameter in Cloud Function validation ([#7892](https://github.com/parse-community/parse-server/issues/7892)) ([48bd512](https://github.com/parse-community/parse-server/commit/48bd512eeb47666967dff8c5e723ddc5b7801daa))
|
||||
|
||||
## [5.2.1-alpha.1](https://github.com/parse-community/parse-server/compare/5.2.0...5.2.1-alpha.1) (2022-03-26)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* return correct response when revert is used in beforeSave ([#7839](https://github.com/parse-community/parse-server/issues/7839)) ([f63fb2b](https://github.com/parse-community/parse-server/commit/f63fb2b338c908f0e7a648d338c26b9daa50c8f2))
|
||||
|
||||
# [5.2.0-alpha.3](https://github.com/parse-community/parse-server/compare/5.2.0-alpha.2...5.2.0-alpha.3) (2022-03-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* security bump minimist from 1.2.5 to 1.2.6 ([#7884](https://github.com/parse-community/parse-server/issues/7884)) ([c5cf282](https://github.com/parse-community/parse-server/commit/c5cf282d11ffdc023764f8e7539a2bd6bc246fe1))
|
||||
|
||||
# [5.2.0-alpha.2](https://github.com/parse-community/parse-server/compare/5.2.0-alpha.1...5.2.0-alpha.2) (2022-03-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* sensitive keyword detection may produce false positives ([#7881](https://github.com/parse-community/parse-server/issues/7881)) ([0d6f9e9](https://github.com/parse-community/parse-server/commit/0d6f9e951d9e186e95e96d8869066ce7022bad02))
|
||||
|
||||
# [5.2.0-alpha.1](https://github.com/parse-community/parse-server/compare/5.1.1...5.2.0-alpha.1) (2022-03-23)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* improved LiveQuery error logging with additional information ([#7837](https://github.com/parse-community/parse-server/issues/7837)) ([443a509](https://github.com/parse-community/parse-server/commit/443a5099059538d379fe491793a5871fcbb4f377))
|
||||
|
||||
# [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)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add user-defined schema and migrations ([#7418](https://github.com/parse-community/parse-server/issues/7418)) ([25d5c30](https://github.com/parse-community/parse-server/commit/25d5c30be2111be332eb779eb0697774a17da7af))
|
||||
|
||||
# [5.0.0-alpha.4](https://github.com/parse-community/parse-server/compare/5.0.0-alpha.3...5.0.0-alpha.4) (2021-10-31)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add support for Postgres 14 ([#7644](https://github.com/parse-community/parse-server/issues/7644)) ([090350a](https://github.com/parse-community/parse-server/commit/090350a7a0fac945394ca1cb24b290316ef06aa7))
|
||||
|
||||
# [5.0.0-alpha.3](https://github.com/parse-community/parse-server/compare/5.0.0-alpha.2...5.0.0-alpha.3) (2021-10-29)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* combined `and` query with relational query condition returns incorrect results ([#7593](https://github.com/parse-community/parse-server/issues/7593)) ([174886e](https://github.com/parse-community/parse-server/commit/174886e385e091c6bbd4a84891ef95f80b50d05c))
|
||||
|
||||
# [5.0.0-alpha.2](https://github.com/parse-community/parse-server/compare/5.0.0-alpha.1...5.0.0-alpha.2) (2021-10-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* setting a field to null does not delete it via GraphQL API ([#7649](https://github.com/parse-community/parse-server/issues/7649)) ([626fad2](https://github.com/parse-community/parse-server/commit/626fad2e71017dcc62196c487de5f908fa43000b))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* To delete a field via the GraphQL API, the field value has to be set to `null`. Previously, setting a field value to `null` would save a null value in the database, which was not according to the [GraphQL specs](https://spec.graphql.org/June2018/#sec-Null-Value). To delete a file field use `file: null`, the previous way of using `file: { file: null }` has become obsolete. ([626fad2](626fad2))
|
||||
|
||||
# [5.0.0-alpha.1](https://github.com/parse-community/parse-server/compare/4.10.4...5.0.0-alpha.1) (2021-10-12)
|
||||
|
||||
## Breaking Changes
|
||||
- Improved schema caching through database real-time hooks. Reduces DB queries, decreases Parse Query execution time and fixes a potential schema memory leak. If multiple Parse Server instances connect to the same DB (for example behind a load balancer), set the [Parse Server Option](https://parseplatform.org/parse-server/api/master/ParseServerOptions.html) `databaseOptions.enableSchemaHooks: true` to enable this feature and keep the schema in sync across all instances. Failing to do so will cause a schema change to not propagate to other instances and re-syncing will only happen when these instances restart. The options `enableSingleSchemaCache` and `schemaCacheTTL` have been removed. To use this feature with MongoDB, a replica set cluster with [change stream](https://docs.mongodb.com/manual/changeStreams/#availability) support is required. (Diamond Lewis, SebC) [#7214](https://github.com/parse-community/parse-server/issues/7214)
|
||||
- Added file upload restriction. File upload is now only allowed for authenticated users by default for improved security. To allow file upload also for Anonymous Users or Public, set the `fileUpload` parameter in the [Parse Server Options](https://parseplatform.org/parse-server/api/master/ParseServerOptions.html) (dblythy, Manuel Trezza) [#7071](https://github.com/parse-community/parse-server/pull/7071)
|
||||
- Removed [parse-server-simple-mailgun-adapter](https://github.com/parse-community/parse-server-simple-mailgun-adapter) dependency; to continue using the adapter it has to be explicitly installed (Manuel Trezza) [#7321](https://github.com/parse-community/parse-server/pull/7321)
|
||||
- Remove support for MongoDB 3.6 which has reached its End-of-Life date and PostgreSQL 10 (Manuel Trezza) [#7315](https://github.com/parse-community/parse-server/pull/7315)
|
||||
- Remove support for Node 10 which has reached its End-of-Life date (Manuel Trezza) [#7314](https://github.com/parse-community/parse-server/pull/7314)
|
||||
- Remove S3 Files Adapter from Parse Server, instead install separately as `@parse/s3-files-adapter` (Manuel Trezza) [#7324](https://github.com/parse-community/parse-server/pull/7324)
|
||||
- Remove Session field `restricted`; the field was a code artifact from a feature that never existed in Open Source Parse Server; if you have been using this field for custom purposes, consider that for new Parse Server installations the field does not exist anymore in the schema, and for existing installations the field default value `false` will not be set anymore when creating a new session (Manuel Trezza) [#7543](https://github.com/parse-community/parse-server/pull/7543)
|
||||
- ci: add node engine version check (Manuel Trezza) [#7574](https://github.com/parse-community/parse-server/pull/7574)
|
||||
|
||||
## Notable Changes
|
||||
- Alphabetical ordered GraphQL API, improved GraphQL Schema cache system and fix GraphQL input reassign issue (Moumouls) [#7344](https://github.com/parse-community/parse-server/issues/7344)
|
||||
- Added Parse Server Security Check to report weak security settings (Manuel Trezza, dblythy) [#7247](https://github.com/parse-community/parse-server/issues/7247)
|
||||
- EXPERIMENTAL: Added new page router with placeholder rendering and localization of custom and feature pages such as password reset and email verification (Manuel Trezza) [#7128](https://github.com/parse-community/parse-server/pull/7128)
|
||||
- EXPERIMENTAL: Added custom routes to easily customize flows for password reset, email verification or build entirely new flows (Manuel Trezza) [#7231](https://github.com/parse-community/parse-server/pull/7231)
|
||||
- Added Deprecation Policy to govern the introduction of breaking changes in a phased pattern that is more predictable for developers (Manuel Trezza) [#7199](https://github.com/parse-community/parse-server/pull/7199)
|
||||
- Add REST API endpoint `/loginAs` to create session of any user with master key; allows to impersonate another user. (GormanFletcher) [#7406](https://github.com/parse-community/parse-server/pull/7406)
|
||||
- Add official support for MongoDB 5.0 (Manuel Trezza) [#7469](https://github.com/parse-community/parse-server/pull/7469)
|
||||
- Added Parse Server Configuration `enforcePrivateUsers`, which will remove public access by default on new Parse.Users (dblythy) [#7319](https://github.com/parse-community/parse-server/pull/7319)
|
||||
|
||||
## Other Changes
|
||||
- Support native mongodb syntax in aggregation pipelines (Raschid JF Rafeally) [#7339](https://github.com/parse-community/parse-server/pull/7339)
|
||||
- Fix error when a not yet inserted job is updated (Antonio Davi Macedo Coelho de Castro) [#7196](https://github.com/parse-community/parse-server/pull/7196)
|
||||
- request.context for afterFind triggers (dblythy) [#7078](https://github.com/parse-community/parse-server/pull/7078)
|
||||
- Winston Logger interpolating stdout to console (dplewis) [#7114](https://github.com/parse-community/parse-server/pull/7114)
|
||||
- Added convenience method `Parse.Cloud.sendEmail(...)` to send email via email adapter in Cloud Code (dblythy) [#7089](https://github.com/parse-community/parse-server/pull/7089)
|
||||
- LiveQuery support for $and, $nor, $containedBy, $geoWithin, $geoIntersects queries (dplewis) [#7113](https://github.com/parse-community/parse-server/pull/7113)
|
||||
- Supporting patterns in LiveQuery server's config parameter `classNames` (Nes-si) [#7131](https://github.com/parse-community/parse-server/pull/7131)
|
||||
- Added `requireAnyUserRoles` and `requireAllUserRoles` for Parse Cloud validator (dblythy) [#7097](https://github.com/parse-community/parse-server/pull/7097)
|
||||
- Support Facebook Limited Login (miguel-s) [#7219](https://github.com/parse-community/parse-server/pull/7219)
|
||||
- Removed Stage name check on aggregate pipelines (BRETT71) [#7237](https://github.com/parse-community/parse-server/pull/7237)
|
||||
- Retry transactions on MongoDB when it fails due to transient error (Antonio Davi Macedo Coelho de Castro) [#7187](https://github.com/parse-community/parse-server/pull/7187)
|
||||
- Bump tests to use Mongo 4.4.4 (Antonio Davi Macedo Coelho de Castro) [#7184](https://github.com/parse-community/parse-server/pull/7184)
|
||||
- Added new account lockout policy option `accountLockout.unlockOnPasswordReset` to automatically unlock account on password reset (Manuel Trezza) [#7146](https://github.com/parse-community/parse-server/pull/7146)
|
||||
- Test Parse Server continuously against all recent MongoDB versions that have not reached their end-of-life support date, added MongoDB compatibility table to Parse Server docs (Manuel Trezza) [#7161](https://github.com/parse-community/parse-server/pull/7161)
|
||||
- Test Parse Server continuously against all recent Node.js versions that have not reached their end-of-life support date, added Node.js compatibility table to Parse Server docs (Manuel Trezza) [7161](https://github.com/parse-community/parse-server/pull/7177)
|
||||
- Throw error on invalid Cloud Function validation configuration (dblythy) [#7154](https://github.com/parse-community/parse-server/pull/7154)
|
||||
- Allow Cloud Validator `options` to be async (dblythy) [#7155](https://github.com/parse-community/parse-server/pull/7155)
|
||||
- Optimize queries on classes with pointer permissions (Pedro Diaz) [#7061](https://github.com/parse-community/parse-server/pull/7061)
|
||||
- Test Parse Server continuously against all relevant Postgres versions (minor versions), added Postgres compatibility table to Parse Server docs (Corey Baker) [#7176](https://github.com/parse-community/parse-server/pull/7176)
|
||||
- Randomize test suite (Diamond Lewis) [#7265](https://github.com/parse-community/parse-server/pull/7265)
|
||||
- LDAP: Properly unbind client on group search error (Diamond Lewis) [#7265](https://github.com/parse-community/parse-server/pull/7265)
|
||||
- Improve data consistency in Push and Job Status update (Diamond Lewis) [#7267](https://github.com/parse-community/parse-server/pull/7267)
|
||||
- Excluding keys that have trailing edges.node when performing GraphQL resolver (Chris Bland) [#7273](https://github.com/parse-community/parse-server/pull/7273)
|
||||
- Added centralized feature deprecation with standardized warning logs (Manuel Trezza) [#7303](https://github.com/parse-community/parse-server/pull/7303)
|
||||
- Use Node.js 15.13.0 in CI (Olle Jonsson) [#7312](https://github.com/parse-community/parse-server/pull/7312)
|
||||
- Fix file upload issue for S3 compatible storage (Linode, DigitalOcean) by avoiding empty tags property when creating a file (Ali Oguzhan Yildiz) [#7300](https://github.com/parse-community/parse-server/pull/7300)
|
||||
- Add building Docker image as CI check (Manuel Trezza) [#7332](https://github.com/parse-community/parse-server/pull/7332)
|
||||
- Add NPM package-lock version check to CI (Manuel Trezza) [#7333](https://github.com/parse-community/parse-server/pull/7333)
|
||||
- Fix incorrect LiveQuery events triggered for multiple subscriptions on the same class with different events [#7341](https://github.com/parse-community/parse-server/pull/7341)
|
||||
- Fix select and excludeKey queries to properly accept JSON string arrays. Also allow nested fields in exclude (Corey Baker) [#7242](https://github.com/parse-community/parse-server/pull/7242)
|
||||
- Fix LiveQuery server crash when using $all query operator on a missing object key (Jason Posthuma) [#7421](https://github.com/parse-community/parse-server/pull/7421)
|
||||
- Added runtime deprecation warnings (Manuel Trezza) [#7451](https://github.com/parse-community/parse-server/pull/7451)
|
||||
- Add ability to pass context of an object via a header, X-Parse-Cloud-Context, for Cloud Code triggers. The header addition allows client SDK's to add context without injecting _context in the body of JSON objects (Corey Baker) [#7437](https://github.com/parse-community/parse-server/pull/7437)
|
||||
- Add CI check to add changelog entry (Manuel Trezza) [#7512](https://github.com/parse-community/parse-server/pull/7512)
|
||||
- Refactor: uniform issue templates across repos (Manuel Trezza) [#7528](https://github.com/parse-community/parse-server/pull/7528)
|
||||
- ci: bump ci environment (Manuel Trezza) [#7539](https://github.com/parse-community/parse-server/pull/7539)
|
||||
- CI now pushes docker images to Docker Hub (Corey Baker) [#7548](https://github.com/parse-community/parse-server/pull/7548)
|
||||
- Allow afterFind and afterLiveQueryEvent to set unsaved pointers and keys (dblythy) [#7310](https://github.com/parse-community/parse-server/pull/7310)
|
||||
- Allow setting descending sort to full text queries (dblythy) [#7496](https://github.com/parse-community/parse-server/pull/7496)
|
||||
- Allow cloud string for ES modules (Daniel Blyth) [#7560](https://github.com/parse-community/parse-server/pull/7560)
|
||||
- docs: Introduce deprecation ID for reference in comments and online search (Manuel Trezza) [#7562](https://github.com/parse-community/parse-server/pull/7562)
|
||||
- refactor: deprecate `Parse.Cloud.httpRequest`; it is recommended to use a HTTP library instead. (Daniel Blyth) [#7595](https://github.com/parse-community/parse-server/pull/7595)
|
||||
- refactor: Modernize HTTPRequest tests (brandongregoryscott) [#7604](https://github.com/parse-community/parse-server/pull/7604)
|
||||
- Allow liveQuery on Session class (Daniel Blyth) [#7554](https://github.com/parse-community/parse-server/pull/7554)
|
||||
@@ -1,191 +0,0 @@
|
||||
# [5.2.0-beta.2](https://github.com/parse-community/parse-server/compare/5.2.0-beta.1...5.2.0-beta.2) (2022-03-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* security bump minimist from 1.2.5 to 1.2.6 ([#7884](https://github.com/parse-community/parse-server/issues/7884)) ([c5cf282](https://github.com/parse-community/parse-server/commit/c5cf282d11ffdc023764f8e7539a2bd6bc246fe1))
|
||||
* sensitive keyword detection may produce false positives ([#7881](https://github.com/parse-community/parse-server/issues/7881)) ([0d6f9e9](https://github.com/parse-community/parse-server/commit/0d6f9e951d9e186e95e96d8869066ce7022bad02))
|
||||
|
||||
# [5.2.0-beta.1](https://github.com/parse-community/parse-server/compare/5.1.1...5.2.0-beta.1) (2022-03-23)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* improved LiveQuery error logging with additional information ([#7837](https://github.com/parse-community/parse-server/issues/7837)) ([443a509](https://github.com/parse-community/parse-server/commit/443a5099059538d379fe491793a5871fcbb4f377))
|
||||
|
||||
# [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)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* bump required node engine to >=12.22.10 ([#7848](https://github.com/parse-community/parse-server/issues/7848)) ([23a3488](https://github.com/parse-community/parse-server/commit/23a3488f15511fafbe0e1d7ff0ef8355f9cb0215))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* This requires Node.js version >=12.22.10. ([23a3488](23a3488))
|
||||
|
||||
# [5.0.0-beta.8](https://github.com/parse-community/parse-server/compare/5.0.0-beta.7...5.0.0-beta.8) (2022-03-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* security vulnerability that allows remote code execution (GHSA-p6h4-93qp-jhcm) ([#7843](https://github.com/parse-community/parse-server/issues/7843)) ([971adb5](https://github.com/parse-community/parse-server/commit/971adb54387b0ede31be05ca407d5f35b4575c83))
|
||||
|
||||
# [5.0.0-beta.7](https://github.com/parse-community/parse-server/compare/5.0.0-beta.6...5.0.0-beta.7) (2022-02-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* security upgrade follow-redirects from 1.14.7 to 1.14.8 ([#7802](https://github.com/parse-community/parse-server/issues/7802)) ([7029b27](https://github.com/parse-community/parse-server/commit/7029b274ca87bc8058617f29865d683dc3b351a1))
|
||||
|
||||
# [5.0.0-beta.6](https://github.com/parse-community/parse-server/compare/5.0.0-beta.5...5.0.0-beta.6) (2022-01-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* security upgrade follow-redirects from 1.14.2 to 1.14.7 ([#7772](https://github.com/parse-community/parse-server/issues/7772)) ([4bd34b1](https://github.com/parse-community/parse-server/commit/4bd34b189bc9f5aa2e70b7e7c1a456e91b6de773))
|
||||
|
||||
# [5.0.0-beta.5](https://github.com/parse-community/parse-server/compare/5.0.0-beta.4...5.0.0-beta.5) (2022-01-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* schema cache not cleared in some cases ([#7771](https://github.com/parse-community/parse-server/issues/7771)) ([3b92fa1](https://github.com/parse-community/parse-server/commit/3b92fa1ca9e8889127a32eba913d68309397ca2c))
|
||||
|
||||
# [5.0.0-beta.4](https://github.com/parse-community/parse-server/compare/5.0.0-beta.3...5.0.0-beta.4) (2021-11-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* unable to use objectId size higher than 19 on GraphQL API ([#7722](https://github.com/parse-community/parse-server/issues/7722)) ([8ee0445](https://github.com/parse-community/parse-server/commit/8ee0445c0aeeb88dff2559b46ade408071d22143))
|
||||
|
||||
# [5.0.0-beta.3](https://github.com/parse-community/parse-server/compare/5.0.0-beta.2...5.0.0-beta.3) (2021-11-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* node engine range has no upper limit to exclude incompatible node versions ([#7693](https://github.com/parse-community/parse-server/issues/7693)) ([6a54dac](https://github.com/parse-community/parse-server/commit/6a54dac24d9fb63a44f311b8d414f4aa64140f32))
|
||||
|
||||
# [5.0.0-beta.2](https://github.com/parse-community/parse-server/compare/5.0.0-beta.1...5.0.0-beta.2) (2021-11-10)
|
||||
|
||||
|
||||
### Reverts
|
||||
|
||||
* refactor: allow ES import for cloud string if package type is module ([#7691](https://github.com/parse-community/parse-server/issues/7691)) ([200d4ba](https://github.com/parse-community/parse-server/commit/200d4ba9a527016a65668738c7728696f443bd53))
|
||||
|
||||
# [5.0.0-beta.1](https://github.com/parse-community/parse-server/compare/4.5.0...5.0.0-beta.1) (2021-11-01)
|
||||
|
||||
### BREAKING CHANGES
|
||||
- Improved schema caching through database real-time hooks. Reduces DB queries, decreases Parse Query execution time and fixes a potential schema memory leak. If multiple Parse Server instances connect to the same DB (for example behind a load balancer), set the [Parse Server Option](https://parseplatform.org/parse-server/api/master/ParseServerOptions.html) `databaseOptions.enableSchemaHooks: true` to enable this feature and keep the schema in sync across all instances. Failing to do so will cause a schema change to not propagate to other instances and re-syncing will only happen when these instances restart. The options `enableSingleSchemaCache` and `schemaCacheTTL` have been removed. To use this feature with MongoDB, a replica set cluster with [change stream](https://docs.mongodb.com/manual/changeStreams/#availability) support is required. (Diamond Lewis, SebC) [#7214](https://github.com/parse-community/parse-server/issues/7214)
|
||||
- Added file upload restriction. File upload is now only allowed for authenticated users by default for improved security. To allow file upload also for Anonymous Users or Public, set the `fileUpload` parameter in the [Parse Server Options](https://parseplatform.org/parse-server/api/master/ParseServerOptions.html) (dblythy, Manuel Trezza) [#7071](https://github.com/parse-community/parse-server/pull/7071)
|
||||
- Removed [parse-server-simple-mailgun-adapter](https://github.com/parse-community/parse-server-simple-mailgun-adapter) dependency; to continue using the adapter it has to be explicitly installed (Manuel Trezza) [#7321](https://github.com/parse-community/parse-server/pull/7321)
|
||||
- Remove support for MongoDB 3.6 which has reached its End-of-Life date and PostgreSQL 10 (Manuel Trezza) [#7315](https://github.com/parse-community/parse-server/pull/7315)
|
||||
- Remove support for Node 10 which has reached its End-of-Life date (Manuel Trezza) [#7314](https://github.com/parse-community/parse-server/pull/7314)
|
||||
- Remove S3 Files Adapter from Parse Server, instead install separately as `@parse/s3-files-adapter` (Manuel Trezza) [#7324](https://github.com/parse-community/parse-server/pull/7324)
|
||||
- Remove Session field `restricted`; the field was a code artifact from a feature that never existed in Open Source Parse Server; if you have been using this field for custom purposes, consider that for new Parse Server installations the field does not exist anymore in the schema, and for existing installations the field default value `false` will not be set anymore when creating a new session (Manuel Trezza) [#7543](https://github.com/parse-community/parse-server/pull/7543)
|
||||
- ci: add node engine version check (Manuel Trezza) [#7574](https://github.com/parse-community/parse-server/pull/7574)
|
||||
- To delete a field via the GraphQL API, the field value has to be set to `null`. Previously, setting a field value to `null` would save a null value in the database, which was not according to the [GraphQL specs](https://spec.graphql.org/June2018/#sec-Null-Value). To delete a file field use `file: null`, the previous way of using `file: { file: null }` has become obsolete. ([626fad2](626fad2))
|
||||
|
||||
### Notable Changes
|
||||
- Alphabetical ordered GraphQL API, improved GraphQL Schema cache system and fix GraphQL input reassign issue (Moumouls) [#7344](https://github.com/parse-community/parse-server/issues/7344)
|
||||
- Added Parse Server Security Check to report weak security settings (Manuel Trezza, dblythy) [#7247](https://github.com/parse-community/parse-server/issues/7247)
|
||||
- EXPERIMENTAL: Added new page router with placeholder rendering and localization of custom and feature pages such as password reset and email verification (Manuel Trezza) [#7128](https://github.com/parse-community/parse-server/pull/7128)
|
||||
- EXPERIMENTAL: Added custom routes to easily customize flows for password reset, email verification or build entirely new flows (Manuel Trezza) [#7231](https://github.com/parse-community/parse-server/pull/7231)
|
||||
- Added Deprecation Policy to govern the introduction of breaking changes in a phased pattern that is more predictable for developers (Manuel Trezza) [#7199](https://github.com/parse-community/parse-server/pull/7199)
|
||||
- Add REST API endpoint `/loginAs` to create session of any user with master key; allows to impersonate another user. (GormanFletcher) [#7406](https://github.com/parse-community/parse-server/pull/7406)
|
||||
- Add official support for MongoDB 5.0 (Manuel Trezza) [#7469](https://github.com/parse-community/parse-server/pull/7469)
|
||||
- Added Parse Server Configuration `enforcePrivateUsers`, which will remove public access by default on new Parse.Users (dblythy) [#7319](https://github.com/parse-community/parse-server/pull/7319)
|
||||
* add support for Postgres 14 ([#7644](https://github.com/parse-community/parse-server/issues/7644)) ([090350a](https://github.com/parse-community/parse-server/commit/090350a7a0fac945394ca1cb24b290316ef06aa7))
|
||||
* add user-defined schema and migrations ([#7418](https://github.com/parse-community/parse-server/issues/7418)) ([25d5c30](https://github.com/parse-community/parse-server/commit/25d5c30be2111be332eb779eb0697774a17da7af))
|
||||
* setting a field to null does not delete it via GraphQL API ([#7649](https://github.com/parse-community/parse-server/issues/7649)) ([626fad2](https://github.com/parse-community/parse-server/commit/626fad2e71017dcc62196c487de5f908fa43000b))
|
||||
* combined `and` query with relational query condition returns incorrect results ([#7593](https://github.com/parse-community/parse-server/issues/7593)) ([174886e](https://github.com/parse-community/parse-server/commit/174886e385e091c6bbd4a84891ef95f80b50d05c))
|
||||
|
||||
### Other Changes
|
||||
- Support native mongodb syntax in aggregation pipelines (Raschid JF Rafeally) [#7339](https://github.com/parse-community/parse-server/pull/7339)
|
||||
- Fix error when a not yet inserted job is updated (Antonio Davi Macedo Coelho de Castro) [#7196](https://github.com/parse-community/parse-server/pull/7196)
|
||||
- request.context for afterFind triggers (dblythy) [#7078](https://github.com/parse-community/parse-server/pull/7078)
|
||||
- Winston Logger interpolating stdout to console (dplewis) [#7114](https://github.com/parse-community/parse-server/pull/7114)
|
||||
- Added convenience method `Parse.Cloud.sendEmail(...)` to send email via email adapter in Cloud Code (dblythy) [#7089](https://github.com/parse-community/parse-server/pull/7089)
|
||||
- LiveQuery support for $and, $nor, $containedBy, $geoWithin, $geoIntersects queries (dplewis) [#7113](https://github.com/parse-community/parse-server/pull/7113)
|
||||
- Supporting patterns in LiveQuery server's config parameter `classNames` (Nes-si) [#7131](https://github.com/parse-community/parse-server/pull/7131)
|
||||
- Added `requireAnyUserRoles` and `requireAllUserRoles` for Parse Cloud validator (dblythy) [#7097](https://github.com/parse-community/parse-server/pull/7097)
|
||||
- Support Facebook Limited Login (miguel-s) [#7219](https://github.com/parse-community/parse-server/pull/7219)
|
||||
- Removed Stage name check on aggregate pipelines (BRETT71) [#7237](https://github.com/parse-community/parse-server/pull/7237)
|
||||
- Retry transactions on MongoDB when it fails due to transient error (Antonio Davi Macedo Coelho de Castro) [#7187](https://github.com/parse-community/parse-server/pull/7187)
|
||||
- Bump tests to use Mongo 4.4.4 (Antonio Davi Macedo Coelho de Castro) [#7184](https://github.com/parse-community/parse-server/pull/7184)
|
||||
- Added new account lockout policy option `accountLockout.unlockOnPasswordReset` to automatically unlock account on password reset (Manuel Trezza) [#7146](https://github.com/parse-community/parse-server/pull/7146)
|
||||
- Test Parse Server continuously against all recent MongoDB versions that have not reached their end-of-life support date, added MongoDB compatibility table to Parse Server docs (Manuel Trezza) [#7161](https://github.com/parse-community/parse-server/pull/7161)
|
||||
- Test Parse Server continuously against all recent Node.js versions that have not reached their end-of-life support date, added Node.js compatibility table to Parse Server docs (Manuel Trezza) [7161](https://github.com/parse-community/parse-server/pull/7177)
|
||||
- Throw error on invalid Cloud Function validation configuration (dblythy) [#7154](https://github.com/parse-community/parse-server/pull/7154)
|
||||
- Allow Cloud Validator `options` to be async (dblythy) [#7155](https://github.com/parse-community/parse-server/pull/7155)
|
||||
- Optimize queries on classes with pointer permissions (Pedro Diaz) [#7061](https://github.com/parse-community/parse-server/pull/7061)
|
||||
- Test Parse Server continuously against all relevant Postgres versions (minor versions), added Postgres compatibility table to Parse Server docs (Corey Baker) [#7176](https://github.com/parse-community/parse-server/pull/7176)
|
||||
- Randomize test suite (Diamond Lewis) [#7265](https://github.com/parse-community/parse-server/pull/7265)
|
||||
- LDAP: Properly unbind client on group search error (Diamond Lewis) [#7265](https://github.com/parse-community/parse-server/pull/7265)
|
||||
- Improve data consistency in Push and Job Status update (Diamond Lewis) [#7267](https://github.com/parse-community/parse-server/pull/7267)
|
||||
- Excluding keys that have trailing edges.node when performing GraphQL resolver (Chris Bland) [#7273](https://github.com/parse-community/parse-server/pull/7273)
|
||||
- Added centralized feature deprecation with standardized warning logs (Manuel Trezza) [#7303](https://github.com/parse-community/parse-server/pull/7303)
|
||||
- Use Node.js 15.13.0 in CI (Olle Jonsson) [#7312](https://github.com/parse-community/parse-server/pull/7312)
|
||||
- Fix file upload issue for S3 compatible storage (Linode, DigitalOcean) by avoiding empty tags property when creating a file (Ali Oguzhan Yildiz) [#7300](https://github.com/parse-community/parse-server/pull/7300)
|
||||
- Add building Docker image as CI check (Manuel Trezza) [#7332](https://github.com/parse-community/parse-server/pull/7332)
|
||||
- Add NPM package-lock version check to CI (Manuel Trezza) [#7333](https://github.com/parse-community/parse-server/pull/7333)
|
||||
- Fix incorrect LiveQuery events triggered for multiple subscriptions on the same class with different events [#7341](https://github.com/parse-community/parse-server/pull/7341)
|
||||
- Fix select and excludeKey queries to properly accept JSON string arrays. Also allow nested fields in exclude (Corey Baker) [#7242](https://github.com/parse-community/parse-server/pull/7242)
|
||||
- Fix LiveQuery server crash when using $all query operator on a missing object key (Jason Posthuma) [#7421](https://github.com/parse-community/parse-server/pull/7421)
|
||||
- Added runtime deprecation warnings (Manuel Trezza) [#7451](https://github.com/parse-community/parse-server/pull/7451)
|
||||
- Add ability to pass context of an object via a header, X-Parse-Cloud-Context, for Cloud Code triggers. The header addition allows client SDK's to add context without injecting _context in the body of JSON objects (Corey Baker) [#7437](https://github.com/parse-community/parse-server/pull/7437)
|
||||
- Add CI check to add changelog entry (Manuel Trezza) [#7512](https://github.com/parse-community/parse-server/pull/7512)
|
||||
- Refactor: uniform issue templates across repos (Manuel Trezza) [#7528](https://github.com/parse-community/parse-server/pull/7528)
|
||||
- ci: bump ci environment (Manuel Trezza) [#7539](https://github.com/parse-community/parse-server/pull/7539)
|
||||
- CI now pushes docker images to Docker Hub (Corey Baker) [#7548](https://github.com/parse-community/parse-server/pull/7548)
|
||||
- Allow afterFind and afterLiveQueryEvent to set unsaved pointers and keys (dblythy) [#7310](https://github.com/parse-community/parse-server/pull/7310)
|
||||
- Allow setting descending sort to full text queries (dblythy) [#7496](https://github.com/parse-community/parse-server/pull/7496)
|
||||
- Allow cloud string for ES modules (Daniel Blyth) [#7560](https://github.com/parse-community/parse-server/pull/7560)
|
||||
- docs: Introduce deprecation ID for reference in comments and online search (Manuel Trezza) [#7562](https://github.com/parse-community/parse-server/pull/7562)
|
||||
- refactor: deprecate `Parse.Cloud.httpRequest`; it is recommended to use a HTTP library instead. (Daniel Blyth) [#7595](https://github.com/parse-community/parse-server/pull/7595)
|
||||
- refactor: Modernize HTTPRequest tests (brandongregoryscott) [#7604](https://github.com/parse-community/parse-server/pull/7604)
|
||||
- Allow liveQuery on Session class (Daniel Blyth) [#7554](https://github.com/parse-community/parse-server/pull/7554)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,290 +0,0 @@
|
||||
const core = require('@actions/core');
|
||||
const semver = require('semver');
|
||||
const yaml = require('yaml');
|
||||
const fs = require('fs').promises;
|
||||
|
||||
/**
|
||||
* This checks the CI version of an environment variable in a YAML file
|
||||
* against a list of released versions of a package.
|
||||
*/
|
||||
class CiVersionCheck {
|
||||
|
||||
/**
|
||||
* The constructor.
|
||||
* @param {Object} config The config.
|
||||
* @param {String} config.packageName The package name to check.
|
||||
* @param {String} config.packageSupportUrl The URL to the package website
|
||||
* that shows the End-of-Life support dates.
|
||||
* @param {String} config.yamlFilePath The path to the GitHub workflow YAML
|
||||
* file that contains the tests.
|
||||
* @param {String} config.ciEnvironmentsKeyPath The key path in the CI YAML
|
||||
* file to the environment specifications.
|
||||
* @param {String} config.ciVersionKey The key in the CI YAML file to
|
||||
* determine the package version.
|
||||
* @param {Array<String>} config.releasedVersions The released versions of
|
||||
* the package to check against.
|
||||
* @param {Array<String>} config.ignoreReleasedVersions The versions to
|
||||
* ignore when checking whether the CI tests against the latest versions.
|
||||
* This can be used in case there is a package release for which Parse
|
||||
* Server compatibility is not required.
|
||||
* @param {String} [config.latestComponent='patch'] The version component
|
||||
* (`major`, `minor`, `patch`) that must be the latest released version.
|
||||
* Default is `patch`.
|
||||
*
|
||||
* For example:
|
||||
* - Released versions: 1.0.0, 1.2.0, 1.2.1, 1.3.0, 1.3.1, 2.0.0
|
||||
* - Tested version: 1.2.0
|
||||
*
|
||||
* If the latest version component is `patch`, then the check would
|
||||
* fail and recommend an upgrade to version 1.2.1 and to add additional
|
||||
* tests against 1.3.1 and 2.0.0.
|
||||
* If the latest version component is `minor` then the check would
|
||||
* fail and recommend an upgrade to version 1.3.0 and to add an additional
|
||||
* test against 2.0.0.
|
||||
* If the latest version component is `major` then the check would
|
||||
* fail and recommend an upgrade to version 2.0.0.
|
||||
*/
|
||||
constructor(config) {
|
||||
const {
|
||||
packageName,
|
||||
packageSupportUrl,
|
||||
yamlFilePath,
|
||||
ciEnvironmentsKeyPath,
|
||||
ciVersionKey,
|
||||
releasedVersions,
|
||||
ignoreReleasedVersions = [],
|
||||
latestComponent = CiVersionCheck.versionComponents.patch,
|
||||
} = config;
|
||||
|
||||
// Ensure required params are set
|
||||
if ([
|
||||
packageName,
|
||||
packageSupportUrl,
|
||||
yamlFilePath,
|
||||
ciEnvironmentsKeyPath,
|
||||
ciVersionKey,
|
||||
releasedVersions,
|
||||
].includes(undefined)) {
|
||||
throw 'invalid configuration';
|
||||
}
|
||||
|
||||
if (!Object.keys(CiVersionCheck.versionComponents).includes(latestComponent)) {
|
||||
throw 'invalid configuration for latestComponent';
|
||||
}
|
||||
|
||||
this.packageName = packageName;
|
||||
this.packageSupportUrl = packageSupportUrl;
|
||||
this.yamlFilePath = yamlFilePath;
|
||||
this.ciEnvironmentsKeyPath = ciEnvironmentsKeyPath;
|
||||
this.ciVersionKey = ciVersionKey;
|
||||
this.releasedVersions = releasedVersions;
|
||||
this.ignoreReleasedVersions = ignoreReleasedVersions;
|
||||
this.latestComponent = latestComponent;
|
||||
}
|
||||
|
||||
/**
|
||||
* The definition of version components.
|
||||
*/
|
||||
static get versionComponents() {
|
||||
return Object.freeze({
|
||||
major: 'major',
|
||||
minor: 'minor',
|
||||
patch: 'patch',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the test environments as specified in the YAML file.
|
||||
*/
|
||||
async getTests() {
|
||||
try {
|
||||
// Get CI workflow
|
||||
const ciYaml = await fs.readFile(this.yamlFilePath, 'utf-8');
|
||||
const ci = yaml.parse(ciYaml);
|
||||
|
||||
// Extract package versions
|
||||
let versions = this.ciEnvironmentsKeyPath.split('.').reduce((o,k) => o !== undefined ? o[k] : undefined, ci);
|
||||
versions = Object.entries(versions)
|
||||
.map(entry => entry[1])
|
||||
.filter(entry => entry[this.ciVersionKey]);
|
||||
|
||||
return versions;
|
||||
} catch (e) {
|
||||
throw `Failed to determine ${this.packageName} versions from CI YAML file with error: ${e}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the package versions which are missing in the CI environment.
|
||||
* @param {Array<String>} releasedVersions The released versions; need to
|
||||
* be sorted descending.
|
||||
* @param {Array<String>} testedVersions The tested versions.
|
||||
* @param {String} versionComponent The latest version component.
|
||||
* @returns {Array<String>} The untested versions.
|
||||
*/
|
||||
getUntestedVersions(releasedVersions, testedVersions, versionComponent) {
|
||||
// Use these example values for debugging the version range logic below
|
||||
// versionComponent = CiVersionCheck.versionComponents.patch;
|
||||
// this.ignoreReleasedVersions = ['<4.4.0', '~4.7.0'];
|
||||
// testedVersions = ['4.4.3'];
|
||||
// releasedVersions = [
|
||||
// '5.0.0-rc0',
|
||||
// '5.0.0',
|
||||
// '4.9.1',
|
||||
// '4.9.0',
|
||||
// '4.8.1',
|
||||
// '4.8.0',
|
||||
// '4.7.1',
|
||||
// '4.7.0',
|
||||
// '4.4.3',
|
||||
// '4.4.2',
|
||||
// '4.4.0',
|
||||
// '4.1.0',
|
||||
// '3.5.0',
|
||||
// ];
|
||||
|
||||
// Determine operator for range comparison
|
||||
const operator = versionComponent == CiVersionCheck.versionComponents.major
|
||||
? '>='
|
||||
: versionComponent == CiVersionCheck.versionComponents.minor
|
||||
? '^'
|
||||
: '~'
|
||||
|
||||
// Get all untested versions
|
||||
const untestedVersions = releasedVersions.reduce((m, v) => {
|
||||
// If the version should be ignored, skip it
|
||||
if (this.ignoreReleasedVersions.length > 0 && semver.satisfies(v, this.ignoreReleasedVersions.join(' || '))) {
|
||||
return m;
|
||||
}
|
||||
// If the version is a pre-release, skip it
|
||||
if ((semver.prerelease(v) || []).length > 0) {
|
||||
return m;
|
||||
}
|
||||
// If a satisfying version has already been added to untested, skip it
|
||||
if (semver.maxSatisfying(m, `${operator}${v}`)) {
|
||||
return m;
|
||||
}
|
||||
// If a satisfying version is already tested, skip it
|
||||
if (semver.maxSatisfying(testedVersions, `${operator}${v}`)) {
|
||||
return m;
|
||||
}
|
||||
// Add version
|
||||
m.push(v);
|
||||
return m;
|
||||
}, []);
|
||||
|
||||
return untestedVersions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the latest version for a given version and component.
|
||||
* @param {Array<String>} versions The versions in which to search.
|
||||
* @param {String} version The version for which a newer version
|
||||
* should be searched.
|
||||
* @param {String} versionComponent The version component up to
|
||||
* which the latest version should be checked.
|
||||
* @returns {String|undefined} The newer version.
|
||||
*/
|
||||
getNewerVersion(versions, version, versionComponent) {
|
||||
// Determine operator for range comparison
|
||||
const operator = versionComponent == CiVersionCheck.versionComponents.major
|
||||
? '>='
|
||||
: versionComponent == CiVersionCheck.versionComponents.minor
|
||||
? '^'
|
||||
: '~'
|
||||
const latest = semver.maxSatisfying(versions, `${operator}${version}`);
|
||||
|
||||
// If the version should be ignored, skip it
|
||||
if (this.ignoreReleasedVersions.length > 0 && semver.satisfies(latest, this.ignoreReleasedVersions.join(' || '))) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Return the latest version if it is newer than any currently used version
|
||||
return semver.gt(latest, version) ? latest : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* This validates that the given versions strictly follow semver
|
||||
* syntax.
|
||||
* @param {Array<String>} versions The versions to check.
|
||||
*/
|
||||
_validateVersionSyntax(versions) {
|
||||
for (const version of versions) {
|
||||
if (!semver.valid(version)) {
|
||||
throw version;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the check.
|
||||
*/
|
||||
async check() {
|
||||
try {
|
||||
console.log(`\nChecking ${this.packageName} versions in CI environments...`);
|
||||
|
||||
// Validate released versions syntax
|
||||
try {
|
||||
this._validateVersionSyntax(this.releasedVersions);
|
||||
} catch (e) {
|
||||
core.setFailed(`Failed to check ${this.packageName} versions because released version '${e}' does not follow semver syntax (x.y.z).`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Sort versions descending
|
||||
semver.sort(this.releasedVersions).reverse()
|
||||
|
||||
// Get tested package versions from CI
|
||||
const tests = await this.getTests();
|
||||
|
||||
// Is true if any of the checks failed
|
||||
let failed = false;
|
||||
|
||||
// Check whether each tested version is the latest patch
|
||||
for (const test of tests) {
|
||||
const version = test[this.ciVersionKey];
|
||||
|
||||
// Validate version syntax
|
||||
try {
|
||||
this._validateVersionSyntax([version]);
|
||||
} catch (e) {
|
||||
core.setFailed(`Failed to check ${this.packageName} versions because environment version '${e}' does not follow semver syntax (x.y.z).`);
|
||||
return;
|
||||
}
|
||||
|
||||
const newer = this.getNewerVersion(this.releasedVersions, version, this.latestComponent);
|
||||
if (newer) {
|
||||
console.log(`❌ CI environment '${test.name}' uses an old ${this.packageName} ${this.latestComponent} version ${version} instead of ${newer}.`);
|
||||
failed = true;
|
||||
} else {
|
||||
console.log(`✅ CI environment '${test.name}' uses the latest ${this.packageName} ${this.latestComponent} version ${version}.`);
|
||||
}
|
||||
}
|
||||
|
||||
// Check whether there is a newer component version available that is not tested
|
||||
const testedVersions = tests.map(test => test[this.ciVersionKey]);
|
||||
const untested = this.getUntestedVersions(this.releasedVersions, testedVersions, this.latestComponent);
|
||||
if (untested.length > 0) {
|
||||
console.log(`❌ CI does not have environments using the following versions of ${this.packageName}: ${untested.join(', ')}.`);
|
||||
failed = true;
|
||||
} else {
|
||||
console.log(`✅ CI has environments using all recent versions of ${this.packageName}.`);
|
||||
}
|
||||
|
||||
if (failed) {
|
||||
core.setFailed(
|
||||
`CI environments are not up-to-date with the latest ${this.packageName} versions.` +
|
||||
`\n\nCheck the error messages above and update the ${this.packageName} versions in the CI YAML ` +
|
||||
`file.\n\nℹ️ Additionally, there may be versions of ${this.packageName} that have reached their official end-of-life ` +
|
||||
`support date and should be removed from the CI, see ${this.packageSupportUrl}.`
|
||||
);
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
const msg = `Failed to check ${this.packageName} versions with error: ${e}`;
|
||||
core.setFailed(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = CiVersionCheck;
|
||||
@@ -1,69 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const CiVersionCheck = require('./CiVersionCheck');
|
||||
const mongoVersionList = require('mongodb-version-list');
|
||||
const allNodeVersions = require('all-node-versions');
|
||||
|
||||
async function check() {
|
||||
// Run checks
|
||||
await checkMongoDbVersions();
|
||||
await checkNodeVersions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the MongoDB versions used in test environments.
|
||||
*/
|
||||
async function checkMongoDbVersions() {
|
||||
const releasedVersions = await new Promise((resolve, reject) => {
|
||||
mongoVersionList(function (error, versions) {
|
||||
if (error) {
|
||||
reject(error);
|
||||
}
|
||||
resolve(versions);
|
||||
});
|
||||
});
|
||||
|
||||
await new CiVersionCheck({
|
||||
packageName: 'MongoDB',
|
||||
packageSupportUrl: 'https://www.mongodb.com/support-policy',
|
||||
yamlFilePath: './.github/workflows/ci.yml',
|
||||
ciEnvironmentsKeyPath: 'jobs.check-mongo.strategy.matrix.include',
|
||||
ciVersionKey: 'MONGODB_VERSION',
|
||||
releasedVersions,
|
||||
latestComponent: CiVersionCheck.versionComponents.path,
|
||||
ignoreReleasedVersions: [
|
||||
'<4.0.0', // Versions reached their MongoDB end-of-life support date
|
||||
'~4.1.0', // Development release according to MongoDB support
|
||||
'~4.3.0', // Development release according to MongoDB support
|
||||
'~4.7.0', // Development release according to MongoDB support
|
||||
|
||||
'4.0.26', // Temporarily disabled because not yet available for download via mongodb-runner
|
||||
],
|
||||
}).check();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the Nodejs versions used in test environments.
|
||||
*/
|
||||
async function checkNodeVersions() {
|
||||
const allVersions = await allNodeVersions();
|
||||
const releasedVersions = allVersions.versions;
|
||||
|
||||
await new CiVersionCheck({
|
||||
packageName: 'Node.js',
|
||||
packageSupportUrl: 'https://github.com/nodejs/node/blob/master/CHANGELOG.md',
|
||||
yamlFilePath: './.github/workflows/ci.yml',
|
||||
ciEnvironmentsKeyPath: 'jobs.check-mongo.strategy.matrix.include',
|
||||
ciVersionKey: 'NODE_VERSION',
|
||||
releasedVersions,
|
||||
latestComponent: CiVersionCheck.versionComponents.minor,
|
||||
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
|
||||
'>=15.0.0 <16.0.0', // These versions have reached their end-of-life support date
|
||||
'>=19.0.0', // These versions are not officially supported yet
|
||||
],
|
||||
}).check();
|
||||
}
|
||||
|
||||
check();
|
||||
@@ -1,194 +0,0 @@
|
||||
const core = require('@actions/core');
|
||||
const semver = require('semver');
|
||||
const fs = require('fs').promises;
|
||||
const path = require('path');
|
||||
|
||||
/**
|
||||
* This checks whether any package dependency requires a minimum node engine
|
||||
* version higher than the host package.
|
||||
*/
|
||||
class NodeEngineCheck {
|
||||
|
||||
/**
|
||||
* The constructor.
|
||||
* @param {Object} config The config.
|
||||
* @param {String} config.nodeModulesPath The path to the node_modules directory.
|
||||
* @param {String} config.packageJsonPath The path to the parent package.json file.
|
||||
*/
|
||||
constructor(config) {
|
||||
const {
|
||||
nodeModulesPath,
|
||||
packageJsonPath,
|
||||
} = config;
|
||||
|
||||
// Ensure required params are set
|
||||
if ([
|
||||
nodeModulesPath,
|
||||
packageJsonPath,
|
||||
].includes(undefined)) {
|
||||
throw 'invalid configuration';
|
||||
}
|
||||
|
||||
this.nodeModulesPath = nodeModulesPath;
|
||||
this.packageJsonPath = packageJsonPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of `package.json` files under the given path and subdirectories.
|
||||
* @param {String} [basePath] The base path for recursive directory search.
|
||||
*/
|
||||
async getPackageFiles(basePath = this.nodeModulesPath) {
|
||||
try {
|
||||
// Declare file list
|
||||
const files = []
|
||||
|
||||
// Get files
|
||||
const dirents = await fs.readdir(basePath, { withFileTypes: true });
|
||||
const validFiles = dirents.filter(d => d.name.toLowerCase() == 'package.json').map(d => path.join(basePath, d.name));
|
||||
files.push(...validFiles);
|
||||
|
||||
// For each directory entry
|
||||
for (const dirent of dirents) {
|
||||
if (dirent.isDirectory()) {
|
||||
const subFiles = await this.getPackageFiles(path.join(basePath, dirent.name));
|
||||
files.push(...subFiles);
|
||||
}
|
||||
}
|
||||
return files;
|
||||
} catch (e) {
|
||||
throw `Failed to get package.json files in ${this.nodeModulesPath} with error: ${e}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts and returns the node engine versions of the given package.json
|
||||
* files.
|
||||
* @param {String[]} files The package.json files.
|
||||
* @param {Boolean} clean Is true if packages with undefined node versions
|
||||
* should be removed from the results.
|
||||
* @returns {Object[]} A list of results.
|
||||
*/
|
||||
async getNodeVersion({ files, clean = false }) {
|
||||
|
||||
// Declare response
|
||||
let response = [];
|
||||
|
||||
// For each file
|
||||
for (const file of files) {
|
||||
// Get node version
|
||||
const contentString = await fs.readFile(file, 'utf-8');
|
||||
try {
|
||||
const contentJson = JSON.parse(contentString);
|
||||
const version = ((contentJson || {}).engines || {}).node;
|
||||
|
||||
// Add response
|
||||
response.push({
|
||||
file: file,
|
||||
nodeVersion: version
|
||||
});
|
||||
} catch(e) {
|
||||
console.log(`Ignoring file because it is not valid JSON: ${file}`);
|
||||
core.warning(`Ignoring file because it is not valid JSON: ${file}`);
|
||||
}
|
||||
}
|
||||
|
||||
// If results should be cleaned by removing undefined node versions
|
||||
if (clean) {
|
||||
response = response.filter(r => r.nodeVersion !== undefined);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the highest semver definition that satisfies all versions
|
||||
* in the given list.
|
||||
* @param {String[]} versions The list of semver version ranges.
|
||||
* @param {String} baseVersion The base version of which higher versions should be
|
||||
* determined; as a version (1.2.3), not a range (>=1.2.3).
|
||||
* @returns {String} The highest semver version.
|
||||
*/
|
||||
getHigherVersions({ versions, baseVersion }) {
|
||||
// Add min satisfying node versions
|
||||
const minVersions = versions.map(v => {
|
||||
v.nodeMinVersion = semver.minVersion(v.nodeVersion)
|
||||
return v;
|
||||
});
|
||||
|
||||
// Sort by min version
|
||||
const sortedMinVersions = minVersions.sort((v1, v2) => semver.compare(v1.nodeMinVersion, v2.nodeMinVersion));
|
||||
|
||||
// Filter by higher versions
|
||||
const higherVersions = sortedMinVersions.filter(v => semver.gt(v.nodeMinVersion, baseVersion));
|
||||
// console.log(`getHigherVersions: ${JSON.stringify(higherVersions)}`);
|
||||
return higherVersions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the node version of the parent package.
|
||||
* @return {Object} The parent package info.
|
||||
*/
|
||||
async getParentVersion() {
|
||||
// Get parent package.json version
|
||||
const version = await this.getNodeVersion({ files: [ this.packageJsonPath ], clean: true });
|
||||
// console.log(`getParentVersion: ${JSON.stringify(version)}`);
|
||||
return version[0];
|
||||
}
|
||||
}
|
||||
|
||||
async function check() {
|
||||
// Define paths
|
||||
const nodeModulesPath = path.join(__dirname, '../node_modules');
|
||||
const packageJsonPath = path.join(__dirname, '../package.json');
|
||||
|
||||
// Create check
|
||||
const check = new NodeEngineCheck({
|
||||
nodeModulesPath,
|
||||
packageJsonPath,
|
||||
});
|
||||
|
||||
// Get package node version of parent package
|
||||
const parentVersion = await check.getParentVersion();
|
||||
|
||||
// If parent node version could not be determined
|
||||
if (parentVersion === undefined) {
|
||||
core.setFailed(`Failed to determine node engine version of parent package at ${this.packageJsonPath}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine parent min version
|
||||
const parentMinVersion = semver.minVersion(parentVersion.nodeVersion);
|
||||
|
||||
// Get package.json files
|
||||
const files = await check.getPackageFiles();
|
||||
core.info(`Checking the minimum node version requirement of ${files.length} dependencies`);
|
||||
|
||||
// Get node versions
|
||||
const versions = await check.getNodeVersion({ files, clean: true });
|
||||
|
||||
// Get are dependencies that require a higher node version than the parent package
|
||||
const higherVersions = check.getHigherVersions({ versions, baseVersion: parentMinVersion });
|
||||
|
||||
// Get highest version
|
||||
const highestVersion = higherVersions.map(v => v.nodeMinVersion).pop();
|
||||
|
||||
// If there are higher versions
|
||||
if (higherVersions.length > 0) {
|
||||
console.log(`\nThere are ${higherVersions.length} dependencies that require a higher node engine version than the parent package (${parentVersion.nodeVersion}):`);
|
||||
|
||||
// For each dependency
|
||||
for (const higherVersion of higherVersions) {
|
||||
|
||||
// Get package name
|
||||
const _package = higherVersion.file.split('node_modules/').pop().replace('/package.json', '');
|
||||
console.log(`- ${_package} requires at least node ${higherVersion.nodeMinVersion} (${higherVersion.nodeVersion})`);
|
||||
}
|
||||
console.log('');
|
||||
core.setFailed(`❌ Upgrade the node engine version in package.json to at least '${highestVersion}' to satisfy the dependencies.`);
|
||||
console.log('');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`✅ All dependencies satisfy the node version requirement of the parent package (${parentVersion.nodeVersion}).`);
|
||||
}
|
||||
|
||||
check();
|
||||
Generated
+4671
-11587
File diff suppressed because it is too large
Load Diff
+53
-107
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "parse-server",
|
||||
"version": "5.3.0-alpha.9",
|
||||
"version": "3.4.4",
|
||||
"description": "An express module providing a Parse-compatible API server",
|
||||
"main": "lib/index.js",
|
||||
"repository": {
|
||||
@@ -19,148 +19,94 @@
|
||||
],
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"@apollo/client": "3.5.10",
|
||||
"@apollographql/graphql-playground-html": "1.6.29",
|
||||
"@graphql-tools/links": "8.2.11",
|
||||
"@graphql-tools/stitch": "6.2.4",
|
||||
"@graphql-tools/utils": "6.2.4",
|
||||
"@parse/fs-files-adapter": "1.2.2",
|
||||
"@parse/push-adapter": "4.1.2",
|
||||
"apollo-server-express": "2.25.2",
|
||||
"@parse/fs-files-adapter": "1.0.1",
|
||||
"@parse/push-adapter": "3.0.0",
|
||||
"@parse/s3-files-adapter": "1.2.1",
|
||||
"@parse/simple-mailgun-adapter": "1.1.0",
|
||||
"bcryptjs": "2.4.3",
|
||||
"body-parser": "1.20.0",
|
||||
"commander": "5.1.0",
|
||||
"cors": "2.8.5",
|
||||
"deepcopy": "2.1.0",
|
||||
"express": "4.17.3",
|
||||
"follow-redirects": "1.14.9",
|
||||
"graphql": "15.8.0",
|
||||
"graphql-list-fields": "2.0.2",
|
||||
"graphql-relay": "0.7.0",
|
||||
"graphql-tag": "2.12.6",
|
||||
"graphql-upload": "11.0.0",
|
||||
"body-parser": "1.19.0",
|
||||
"commander": "2.20.0",
|
||||
"deepcopy": "2.0.0",
|
||||
"express": "4.17.0",
|
||||
"follow-redirects": "1.7.0",
|
||||
"intersect": "1.0.1",
|
||||
"jsonwebtoken": "8.5.1",
|
||||
"jwks-rsa": "2.0.5",
|
||||
"ldapjs": "2.3.1",
|
||||
"lodash": "4.17.21",
|
||||
"lru-cache": "6.0.0",
|
||||
"mime": "3.0.0",
|
||||
"mongodb": "4.4.1",
|
||||
"mustache": "4.2.0",
|
||||
"parse": "3.4.1",
|
||||
"pg-monitor": "1.4.1",
|
||||
"pg-promise": "10.11.1",
|
||||
"pluralize": "8.0.0",
|
||||
"redis": "3.1.2",
|
||||
"semver": "7.3.7",
|
||||
"subscriptions-transport-ws": "0.11.0",
|
||||
"lodash": "4.17.11",
|
||||
"lru-cache": "5.1.1",
|
||||
"mime": "2.4.3",
|
||||
"mongodb": "3.2.5",
|
||||
"parse": "2.4.0",
|
||||
"pg-promise": "8.7.2",
|
||||
"redis": "2.8.0",
|
||||
"semver": "6.0.0",
|
||||
"tv4": "1.3.0",
|
||||
"uuid": "8.3.2",
|
||||
"winston": "3.7.2",
|
||||
"winston-daily-rotate-file": "4.6.0",
|
||||
"ws": "8.2.3"
|
||||
"uuid": "3.3.2",
|
||||
"winston": "3.2.1",
|
||||
"winston-daily-rotate-file": "3.9.0",
|
||||
"ws": "7.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@actions/core": "1.2.6",
|
||||
"@babel/cli": "7.10.0",
|
||||
"@babel/core": "7.10.0",
|
||||
"@babel/plugin-proposal-object-rest-spread": "7.10.0",
|
||||
"@babel/plugin-transform-flow-strip-types": "7.9.0",
|
||||
"@babel/preset-env": "7.10.0",
|
||||
"@babel/cli": "7.4.4",
|
||||
"@babel/core": "7.4.5",
|
||||
"@babel/plugin-proposal-object-rest-spread": "7.4.4",
|
||||
"@babel/plugin-transform-flow-strip-types": "7.4.4",
|
||||
"@babel/preset-env": "7.4.5",
|
||||
"@parse/minami": "1.0.0",
|
||||
"@saithodev/semantic-release-backmerge": "2.1.2",
|
||||
"@semantic-release/changelog": "5.0.1",
|
||||
"@semantic-release/commit-analyzer": "8.0.1",
|
||||
"@semantic-release/git": "9.0.0",
|
||||
"@semantic-release/github": "7.2.3",
|
||||
"@semantic-release/npm": "7.1.3",
|
||||
"@semantic-release/release-notes-generator": "9.0.3",
|
||||
"all-node-versions": "8.0.0",
|
||||
"apollo-upload-client": "16.0.0",
|
||||
"apollo-utilities": "1.3.4",
|
||||
"babel-eslint": "10.1.0",
|
||||
"babel-eslint": "10.0.0",
|
||||
"bcrypt-nodejs": "0.0.3",
|
||||
"cross-env": "7.0.2",
|
||||
"cross-env": "5.2.0",
|
||||
"deep-diff": "1.0.2",
|
||||
"eslint": "6.8.0",
|
||||
"eslint-plugin-flowtype": "5.1.3",
|
||||
"flow-bin": "0.119.1",
|
||||
"form-data": "3.0.0",
|
||||
"husky": "4.2.5",
|
||||
"jasmine": "3.5.0",
|
||||
"jasmine-spec-reporter": "7.0.0",
|
||||
"jsdoc": "3.6.3",
|
||||
"eslint": "5.16.0",
|
||||
"eslint-plugin-flowtype": "3.9.0",
|
||||
"flow-bin": "0.98.1",
|
||||
"gaze": "1.1.3",
|
||||
"husky": "2.3.0",
|
||||
"jasmine": "3.4.0",
|
||||
"jasmine-spec-reporter": "4.2.1",
|
||||
"jsdoc": "3.6.2",
|
||||
"jsdoc-babel": "0.5.0",
|
||||
"lint-staged": "10.2.3",
|
||||
"madge": "5.0.1",
|
||||
"mock-files-adapter": "file:spec/dependencies/mock-files-adapter",
|
||||
"mock-mail-adapter": "file:spec/dependencies/mock-mail-adapter",
|
||||
"mongodb-runner": "4.8.1",
|
||||
"mongodb-version-list": "1.0.0",
|
||||
"node-fetch": "3.1.1",
|
||||
"nyc": "15.1.0",
|
||||
"prettier": "2.0.5",
|
||||
"semantic-release": "17.4.6",
|
||||
"yaml": "1.10.0"
|
||||
"lint-staged": "8.1.7",
|
||||
"mongodb-runner": "4.3.2",
|
||||
"nyc": "14.1.1",
|
||||
"prettier": "1.17.1",
|
||||
"supports-color": "6.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"ci:check": "node ./ci/ciCheck.js",
|
||||
"ci:checkNodeEngine": "node ./ci/nodeEngineCheck.js",
|
||||
"definitions": "node ./resources/buildConfigDefinitions.js && prettier --write 'src/Options/*.js'",
|
||||
"definitions": "node ./resources/buildConfigDefinitions.js",
|
||||
"docs": "jsdoc -c ./jsdoc-conf.json",
|
||||
"dev": "npm run build && node bin/dev",
|
||||
"lint": "flow && eslint --cache ./",
|
||||
"lint-fix": "eslint --fix --cache ./",
|
||||
"build": "babel src/ -d lib/ --copy-files",
|
||||
"watch": "babel --watch src/ -d lib/ --copy-files",
|
||||
"test:mongodb:runnerstart": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=$npm_config_dbversion} MONGODB_TOPOLOGY=${MONGODB_TOPOLOGY:=standalone} MONGODB_STORAGE_ENGINE=${MONGODB_STORAGE_ENGINE:=wiredTiger} mongodb-runner start",
|
||||
"test:mongodb:testonly": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=$npm_config_dbversion} MONGODB_TOPOLOGY=${MONGODB_TOPOLOGY:=standalone} MONGODB_STORAGE_ENGINE=${MONGODB_STORAGE_ENGINE:=wiredTiger} TESTING=1 jasmine",
|
||||
"test:mongodb": "npm run test:mongodb:runnerstart --dbversion=$npm_config_dbversion && npm run test:mongodb:testonly --dbversion=$npm_config_dbversion",
|
||||
"test:mongodb:4.0.28": "npm run test:mongodb --dbversion=4.0.28",
|
||||
"test:mongodb:4.2.19": "npm run test:mongodb --dbversion=4.2.19",
|
||||
"test:mongodb:4.4.13": "npm run test:mongodb --dbversion=4.4.13",
|
||||
"test:mongodb:5.0.6": "npm run test:mongodb --dbversion=5.0.6",
|
||||
"test:mongodb:5.1.1": "npm run test:mongodb --dbversion=5.1.1",
|
||||
"test:mongodb:5.2.1": "npm run test:mongodb --dbversion=5.2.1",
|
||||
"posttest:mongodb": "mongodb-runner stop",
|
||||
"pretest": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=5.2.1} MONGODB_TOPOLOGY=${MONGODB_TOPOLOGY:=standalone} MONGODB_STORAGE_ENGINE=${MONGODB_STORAGE_ENGINE:=wiredTiger} mongodb-runner start",
|
||||
"testonly": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=5.2.1} 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:=5.2.1} MONGODB_TOPOLOGY=${MONGODB_TOPOLOGY:=standalone} MONGODB_STORAGE_ENGINE=${MONGODB_STORAGE_ENGINE:=wiredTiger} mongodb-runner stop",
|
||||
"coverage": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=5.2.1} MONGODB_TOPOLOGY=${MONGODB_TOPOLOGY:=standalone} MONGODB_STORAGE_ENGINE=${MONGODB_STORAGE_ENGINE:=wiredTiger} TESTING=1 nyc jasmine",
|
||||
"test": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=4.0.4} MONGODB_STORAGE_ENGINE=mmapv1 TESTING=1 jasmine",
|
||||
"coverage": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=4.0.4} MONGODB_STORAGE_ENGINE=mmapv1 TESTING=1 nyc jasmine",
|
||||
"start": "node ./bin/parse-server",
|
||||
"prettier": "prettier --write {src,spec}/{**/*,*}.js",
|
||||
"prepare": "npm run build",
|
||||
"postinstall": "node -p 'require(\"./postinstall.js\")()'",
|
||||
"madge:circular": "node_modules/.bin/madge ./src --circular"
|
||||
"postinstall": "node -p 'require(\"./postinstall.js\")()'"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.22.10 <19"
|
||||
"node": ">= 8"
|
||||
},
|
||||
"bin": {
|
||||
"parse-server": "bin/parse-server"
|
||||
"parse-server": "./bin/parse-server"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@node-rs/bcrypt": "1.1.0"
|
||||
"bcrypt": "3.0.6",
|
||||
"uws": "10.148.1"
|
||||
},
|
||||
"collective": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parse-server",
|
||||
"logo": "https://opencollective.com/parse-server/logo.txt?reverse=true&variant=binary"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parse-server"
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "lint-staged"
|
||||
}
|
||||
},
|
||||
"lint-staged": {
|
||||
"{src,spec}/{**/*,*}.js": [
|
||||
"{src,spec}/**/*.js": [
|
||||
"prettier --write",
|
||||
"eslint --fix --cache",
|
||||
"eslint --cache",
|
||||
"git add"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<!--
|
||||
This page demonstrates how to localize using a JSON file that contains the
|
||||
translations for each placeholder.
|
||||
-->
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>{{title}}</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>{{heading}}</h1>
|
||||
<p>{{body}}</p>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"en": {
|
||||
"translation": {
|
||||
"title": "Hello!",
|
||||
"heading": "Welcome to {{appName}}!",
|
||||
"body": "We are delighted to welcome you on board."
|
||||
}
|
||||
},
|
||||
"de": {
|
||||
"translation": {
|
||||
"title": "Hallo!",
|
||||
"heading": "Willkommen bei {{appName}}!",
|
||||
"body": "Wir freuen uns, dich begrüßen zu dürfen."
|
||||
}
|
||||
},
|
||||
"de-AT": {
|
||||
"translation": {
|
||||
"title": "Servus!",
|
||||
"heading": "Willkommen bei {{appName}}!",
|
||||
"body": "Wir freuen uns, dich begrüßen zu dürfen."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<!--
|
||||
This page demonstrates how to use custom pages in custom routes.
|
||||
-->
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>{{appName}}</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>{{appName}}</h1>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,24 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<!--
|
||||
This page is displayed when a user opens a verify email link with a security
|
||||
token that is expired or incorrect. This can either mean the user has clicked
|
||||
on a stale link (i.e. re-clicked on the link) or this could be a sign of a
|
||||
malicious user trying to tamper with your app.
|
||||
-->
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>Email Verification</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>{{appName}}</h1>
|
||||
<h1>Expired verification link!</h1>
|
||||
<form method="POST" action="{{{publicServerUrl}}}/apps/{{{appId}}}/resend_verification_email">
|
||||
<input name="username" type="hidden" value="{{{username}}}">
|
||||
<input name="locale" type="hidden" value="{{{locale}}}">
|
||||
<button type="submit">Resend Link</button>
|
||||
</form>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,21 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<!--
|
||||
This page is displayed when a user opens a verify email link with parameters
|
||||
that are missing or incorrect. This can either mean the user has incorrectly
|
||||
entered a link or this could be a sign of a malicious user trying to tamper
|
||||
with your app.
|
||||
If the link contains an expired security token (or the email has already
|
||||
been verified), this page is not displayed, there is another page for that.
|
||||
-->
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>Email Verification</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>{{appName}}</h1>
|
||||
<h1>Invalid verification link!</h1>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,21 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<!--
|
||||
This page is displayed when a user opens a verify email link with a security
|
||||
token that is expired or incorrect, then requests to receive another link,
|
||||
but it fails because the username is invalid or the email has already been
|
||||
verified. This can either mean the user has previously verified the email
|
||||
or this could be a sign of a malicious user trying to tamper with your app.
|
||||
-->
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>Email Verification</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>{{appName}}</h1>
|
||||
<h1>Invalid link!</h1>
|
||||
<p>No link sent. User not found or email already verified.</p>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,19 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<!--
|
||||
This page is displayed when a user opens a verify email link with a
|
||||
security token that is expired, then requests to receive another email
|
||||
with a new link and the email is sent successfully.
|
||||
-->
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>Email Verification</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>{{appName}}</h1>
|
||||
<h1>Link sent!</h1>
|
||||
<p>A new link has been sent. Check your email.</p>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,18 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<!--
|
||||
This page is displayed when a user opens a verify email link and the
|
||||
email gets verified successfully.
|
||||
-->
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>Email Verification</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>{{appName}}</h1>
|
||||
<h1>Email verified!</h1>
|
||||
<p>Successfully verified your email for account: {{username}}.</p>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,65 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<!--
|
||||
This page is displayed when someone clicks a valid 'reset password' link.
|
||||
Users should feel free to add to this page (i.e. branding or security widgets)
|
||||
but should be sure not to delete any of the form inputs or the javascript from the
|
||||
template file. This javascript is what adds the necessary values to authenticate
|
||||
this session with Parse.
|
||||
The query params 'username' and 'app' hold the friendly names for your current user and
|
||||
your app. You should feel free to incorporate their values to make the page more personal.
|
||||
If you are missing form parameters in your POST, Parse will navigate back to this page and
|
||||
add an 'error' query parameter.
|
||||
-->
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>Password Reset</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>{{appName}}</h1>
|
||||
<h1>Reset Your Password</h1>
|
||||
<noscript><p>We apologize, but resetting your password requires javascript</p></noscript>
|
||||
<p>You can set a new Password for your account: {{username}}</p>
|
||||
<br />
|
||||
<p>{{error}}</p>
|
||||
<form id='form' action='{{{publicServerUrl}}}/apps/{{{appId}}}/request_password_reset' method='POST'>
|
||||
<input name='utf-8' type='hidden' value='✓' />
|
||||
<input name="username" type="hidden" id="username" value="{{{username}}}" />
|
||||
<input name="token" type="hidden" id="token" value="{{{token}}}" />
|
||||
<input name="locale" type="hidden" id="locale" value="{{{locale}}}" />
|
||||
|
||||
<p>New Password</p>
|
||||
<input name="new_password" type="password" id="password" />
|
||||
<p>Confirm New Password</p>
|
||||
<input name="confirm_new_password" type="password" id="password_confirm" />
|
||||
<br />
|
||||
<p id="password_match_info"></p>
|
||||
<br />
|
||||
<button id="change_password">Change Password</button>
|
||||
</form>
|
||||
|
||||
<script>
|
||||
window.onload = function() {
|
||||
document.getElementById("password").oninput = validatePassword;
|
||||
document.getElementById("password_confirm").oninput = validatePassword;
|
||||
document.getElementById("change_password").disabled = true;
|
||||
|
||||
function validatePassword() {
|
||||
var pass2 = document.getElementById("password").value;
|
||||
var pass1 = document.getElementById("password_confirm").value;
|
||||
if(pass1 !== pass2) {
|
||||
if(document.getElementById("password_confirm").value) {
|
||||
document.getElementById("change_password").disabled = true;
|
||||
document.getElementById("password_match_info").innerHTML = "Must match the previous entry";
|
||||
}
|
||||
} else {
|
||||
document.getElementById("change_password").disabled = false;
|
||||
document.getElementById("password_match_info").innerHTML = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,19 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<!--
|
||||
This page is displayed when a user opens a password reset link with parameters
|
||||
that are missing or incorrect. This can either mean the user has incorrectly
|
||||
entered a link or this could be a sign of a malicious user trying to tamper
|
||||
with your app.
|
||||
-->
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>Password Reset</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>{{appName}}</h1>
|
||||
<h1>Invalid password reset link!</h1>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,18 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<!--
|
||||
This page is displayed when a user opens a reset password link, then submits
|
||||
the form with a new password and the password gets updated successfully.
|
||||
-->
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>Password Reset</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>{{appName}}</h1>
|
||||
<h1>Success!</h1>
|
||||
<p>Your password has been updated.</p>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,24 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<!--
|
||||
This page is displayed when a user opens a verify email link with a security
|
||||
token that is expired or incorrect. This can either mean the user has clicked
|
||||
on a stale link (i.e. re-clicked on the link) or this could be a sign of a
|
||||
malicious user trying to tamper with your app.
|
||||
-->
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>Email Verification</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>{{appName}}</h1>
|
||||
<h1>Expired verification link!</h1>
|
||||
<form method="POST" action="{{{publicServerUrl}}}/apps/{{{appId}}}/resend_verification_email">
|
||||
<input name="username" type="hidden" value="{{{username}}}">
|
||||
<input name="locale" type="hidden" value="{{{locale}}}">
|
||||
<button type="submit">Resend Link</button>
|
||||
</form>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,21 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<!--
|
||||
This page is displayed when a user opens a verify email link with parameters
|
||||
that are missing or incorrect. This can either mean the user has incorrectly
|
||||
entered a link or this could be a sign of a malicious user trying to tamper
|
||||
with your app.
|
||||
If the link contains an expired security token (or the email has already
|
||||
been verified), this page is not displayed, there is another page for that.
|
||||
-->
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>Email Verification</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>{{appName}}</h1>
|
||||
<h1>Invalid verification link!</h1>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,21 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<!--
|
||||
This page is displayed when a user opens a verify email link with a security
|
||||
token that is expired or incorrect, then requests to receive another link,
|
||||
but it fails because the username is invalid or the email has already been
|
||||
verified. This can either mean the user has previously verified the email
|
||||
or this could be a sign of a malicious user trying to tamper with your app.
|
||||
-->
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>Email Verification</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>{{appName}}</h1>
|
||||
<h1>Invalid link!</h1>
|
||||
<p>No link sent. User not found or email already verified.</p>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,19 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<!--
|
||||
This page is displayed when a user opens a verify email link with a
|
||||
security token that is expired, then requests to receive another email
|
||||
with a new link and the email is sent successfully.
|
||||
-->
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>Email Verification</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>{{appName}}</h1>
|
||||
<h1>Link sent!</h1>
|
||||
<p>A new link has been sent. Check your email.</p>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,18 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<!--
|
||||
This page is displayed when a user opens a verify email link and the
|
||||
email gets verified successfully.
|
||||
-->
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>Email Verification</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>{{appName}}</h1>
|
||||
<h1>Email verified!</h1>
|
||||
<p>Successfully verified your email for account: {{username}}.</p>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,65 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<!--
|
||||
This page is displayed when someone clicks a valid 'reset password' link.
|
||||
Users should feel free to add to this page (i.e. branding or security widgets)
|
||||
but should be sure not to delete any of the form inputs or the javascript from the
|
||||
template file. This javascript is what adds the necessary values to authenticate
|
||||
this session with Parse.
|
||||
The query params 'username' and 'app' hold the friendly names for your current user and
|
||||
your app. You should feel free to incorporate their values to make the page more personal.
|
||||
If you are missing form parameters in your POST, Parse will navigate back to this page and
|
||||
add an 'error' query parameter.
|
||||
-->
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>Password Reset</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>{{appName}}</h1>
|
||||
<h1>Reset Your Password</h1>
|
||||
<noscript><p>We apologize, but resetting your password requires javascript</p></noscript>
|
||||
<p>You can set a new Password for your account: {{username}}</p>
|
||||
<br />
|
||||
<p>{{error}}</p>
|
||||
<form id='form' action='{{{publicServerUrl}}}/apps/{{{appId}}}/request_password_reset' method='POST'>
|
||||
<input name='utf-8' type='hidden' value='✓' />
|
||||
<input name="username" type="hidden" id="username" value="{{{username}}}" />
|
||||
<input name="token" type="hidden" id="token" value="{{{token}}}" />
|
||||
<input name="locale" type="hidden" id="locale" value="{{{locale}}}" />
|
||||
|
||||
<p>New Password</p>
|
||||
<input name="new_password" type="password" id="password" />
|
||||
<p>Confirm New Password</p>
|
||||
<input name="confirm_new_password" type="password" id="password_confirm" />
|
||||
<br />
|
||||
<p id="password_match_info"></p>
|
||||
<br />
|
||||
<button id="change_password">Change Password</button>
|
||||
</form>
|
||||
|
||||
<script>
|
||||
window.onload = function() {
|
||||
document.getElementById("password").oninput = validatePassword;
|
||||
document.getElementById("password_confirm").oninput = validatePassword;
|
||||
document.getElementById("change_password").disabled = true;
|
||||
|
||||
function validatePassword() {
|
||||
var pass2 = document.getElementById("password").value;
|
||||
var pass1 = document.getElementById("password_confirm").value;
|
||||
if(pass1 !== pass2) {
|
||||
if(document.getElementById("password_confirm").value) {
|
||||
document.getElementById("change_password").disabled = true;
|
||||
document.getElementById("password_match_info").innerHTML = "Must match the previous entry";
|
||||
}
|
||||
} else {
|
||||
document.getElementById("change_password").disabled = false;
|
||||
document.getElementById("password_match_info").innerHTML = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,19 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<!--
|
||||
This page is displayed when a user opens a password reset link with parameters
|
||||
that are missing or incorrect. This can either mean the user has incorrectly
|
||||
entered a link or this could be a sign of a malicious user trying to tamper
|
||||
with your app.
|
||||
-->
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>Password Reset</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>{{appName}}</h1>
|
||||
<h1>Invalid password reset link!</h1>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,18 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<!--
|
||||
This page is displayed when a user opens a reset password link, then submits
|
||||
the form with a new password and the password gets updated successfully.
|
||||
-->
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>Password Reset</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>{{appName}}</h1>
|
||||
<h1>Success!</h1>
|
||||
<p>Your password has been updated.</p>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,24 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<!--
|
||||
This page is displayed when a user opens a verify email link with a security
|
||||
token that is expired or incorrect. This can either mean the user has clicked
|
||||
on a stale link (i.e. re-clicked on the link) or this could be a sign of a
|
||||
malicious user trying to tamper with your app.
|
||||
-->
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>Email Verification</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>{{appName}}</h1>
|
||||
<h1>Expired verification link!</h1>
|
||||
<form method="POST" action="{{{publicServerUrl}}}/apps/{{{appId}}}/resend_verification_email">
|
||||
<input name="username" type="hidden" value="{{{username}}}">
|
||||
<input name="locale" type="hidden" value="{{{locale}}}">
|
||||
<button type="submit">Resend Link</button>
|
||||
</form>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,21 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<!--
|
||||
This page is displayed when a user opens a verify email link with parameters
|
||||
that are missing or incorrect. This can either mean the user has incorrectly
|
||||
entered a link or this could be a sign of a malicious user trying to tamper
|
||||
with your app.
|
||||
If the link contains an expired security token (or the email has already
|
||||
been verified), this page is not displayed, there is another page for that.
|
||||
-->
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>Email Verification</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>{{appName}}</h1>
|
||||
<h1>Invalid verification link!</h1>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,21 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<!--
|
||||
This page is displayed when a user opens a verify email link with a security
|
||||
token that is expired or incorrect, then requests to receive another link,
|
||||
but it fails because the username is invalid or the email has already been
|
||||
verified. This can either mean the user has previously verified the email
|
||||
or this could be a sign of a malicious user trying to tamper with your app.
|
||||
-->
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>Email Verification</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>{{appName}}</h1>
|
||||
<h1>Invalid link!</h1>
|
||||
<p>No link sent. User not found or email already verified.</p>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,19 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<!--
|
||||
This page is displayed when a user opens a verify email link with a
|
||||
security token that is expired, then requests to receive another email
|
||||
with a new link and the email is sent successfully.
|
||||
-->
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>Email Verification</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>{{appName}}</h1>
|
||||
<h1>Link sent!</h1>
|
||||
<p>A new link has been sent. Check your email.</p>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,18 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<!--
|
||||
This page is displayed when a user opens a verify email link and the
|
||||
email gets verified successfully.
|
||||
-->
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>Email Verification</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>{{appName}}</h1>
|
||||
<h1>Email verified!</h1>
|
||||
<p>Successfully verified your email for account: {{username}}.</p>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,65 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<!--
|
||||
This page is displayed when someone clicks a valid 'reset password' link.
|
||||
Users should feel free to add to this page (i.e. branding or security widgets)
|
||||
but should be sure not to delete any of the form inputs or the javascript from the
|
||||
template file. This javascript is what adds the necessary values to authenticate
|
||||
this session with Parse.
|
||||
The query params 'username' and 'app' hold the friendly names for your current user and
|
||||
your app. You should feel free to incorporate their values to make the page more personal.
|
||||
If you are missing form parameters in your POST, Parse will navigate back to this page and
|
||||
add an 'error' query parameter.
|
||||
-->
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>Password Reset</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>{{appName}}</h1>
|
||||
<h1>Reset Your Password</h1>
|
||||
<noscript><p>We apologize, but resetting your password requires javascript</p></noscript>
|
||||
<p>You can set a new Password for your account: {{username}}</p>
|
||||
<br />
|
||||
<p>{{error}}</p>
|
||||
<form id='form' action='{{{publicServerUrl}}}/apps/{{{appId}}}/request_password_reset' method='POST'>
|
||||
<input name='utf-8' type='hidden' value='✓' />
|
||||
<input name="username" type="hidden" id="username" value="{{{username}}}" />
|
||||
<input name="token" type="hidden" id="token" value="{{{token}}}" />
|
||||
<input name="locale" type="hidden" id="locale" value="{{{locale}}}" />
|
||||
|
||||
<p>New Password</p>
|
||||
<input name="new_password" type="password" id="password" />
|
||||
<p>Confirm New Password</p>
|
||||
<input name="confirm_new_password" type="password" id="password_confirm" />
|
||||
<br />
|
||||
<p id="password_match_info"></p>
|
||||
<br />
|
||||
<button id="change_password">Change Password</button>
|
||||
</form>
|
||||
|
||||
<script>
|
||||
window.onload = function() {
|
||||
document.getElementById("password").oninput = validatePassword;
|
||||
document.getElementById("password_confirm").oninput = validatePassword;
|
||||
document.getElementById("change_password").disabled = true;
|
||||
|
||||
function validatePassword() {
|
||||
var pass2 = document.getElementById("password").value;
|
||||
var pass1 = document.getElementById("password_confirm").value;
|
||||
if(pass1 !== pass2) {
|
||||
if(document.getElementById("password_confirm").value) {
|
||||
document.getElementById("change_password").disabled = true;
|
||||
document.getElementById("password_match_info").innerHTML = "Must match the previous entry";
|
||||
}
|
||||
} else {
|
||||
document.getElementById("change_password").disabled = false;
|
||||
document.getElementById("password_match_info").innerHTML = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,19 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<!--
|
||||
This page is displayed when a user opens a password reset link with parameters
|
||||
that are missing or incorrect. This can either mean the user has incorrectly
|
||||
entered a link or this could be a sign of a malicious user trying to tamper
|
||||
with your app.
|
||||
-->
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>Password Reset</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>{{appName}}</h1>
|
||||
<h1>Invalid password reset link!</h1>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,18 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<!--
|
||||
This page is displayed when a user opens a reset password link, then submits
|
||||
the form with a new password and the password gets updated successfully.
|
||||
-->
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>Password Reset</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>{{appName}}</h1>
|
||||
<h1>Success!</h1>
|
||||
<p>Your password has been updated.</p>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,124 +0,0 @@
|
||||
/**
|
||||
* Semantic Release Config
|
||||
*/
|
||||
|
||||
const fs = require('fs').promises;
|
||||
const path = require('path');
|
||||
|
||||
// Get env vars
|
||||
const ref = process.env.GITHUB_REF;
|
||||
const serverUrl = process.env.GITHUB_SERVER_URL;
|
||||
const repository = process.env.GITHUB_REPOSITORY;
|
||||
const repositoryUrl = serverUrl + '/' + repository;
|
||||
|
||||
// Declare params
|
||||
const resourcePath = './.releaserc/';
|
||||
const templates = {
|
||||
main: { file: 'template.hbs', text: undefined },
|
||||
header: { file: 'header.hbs', text: undefined },
|
||||
commit: { file: 'commit.hbs', text: undefined },
|
||||
footer: { file: 'footer.hbs', text: undefined },
|
||||
};
|
||||
|
||||
// Declare semantic config
|
||||
async function config() {
|
||||
|
||||
// Get branch
|
||||
const branch = ref.split('/').pop();
|
||||
console.log(`Running on branch: ${branch}`);
|
||||
|
||||
// Set changelog file
|
||||
const changelogFile = `./changelogs/CHANGELOG_${branch}.md`;
|
||||
console.log(`Changelog file output to: ${changelogFile}`);
|
||||
|
||||
// Load template file contents
|
||||
await loadTemplates();
|
||||
|
||||
const config = {
|
||||
branches: [
|
||||
'release',
|
||||
{ name: 'alpha', prerelease: true },
|
||||
{ name: 'beta', prerelease: true },
|
||||
'next-major',
|
||||
// Long-Term-Support branches
|
||||
// { name: 'release-1', range: '1.x.x', channel: '1.x' },
|
||||
// { name: 'release-2', range: '2.x.x', channel: '2.x' },
|
||||
// { name: 'release-3', range: '3.x.x', channel: '3.x' },
|
||||
// { name: 'release-4', range: '4.x.x', channel: '4.x' },
|
||||
],
|
||||
dryRun: false,
|
||||
debug: true,
|
||||
ci: true,
|
||||
tagFormat: '${version}',
|
||||
plugins: [
|
||||
['@semantic-release/commit-analyzer', {
|
||||
preset: 'angular',
|
||||
releaseRules: [
|
||||
{ type: 'docs', scope: 'README', release: 'patch' },
|
||||
{ scope: 'no-release', release: false },
|
||||
],
|
||||
parserOpts: {
|
||||
noteKeywords: [ 'BREAKING CHANGE', 'BREAKING CHANGES', 'BREAKING' ],
|
||||
},
|
||||
}],
|
||||
['@semantic-release/release-notes-generator', {
|
||||
preset: 'angular',
|
||||
parserOpts: {
|
||||
noteKeywords: ['BREAKING CHANGE', 'BREAKING CHANGES', 'BREAKING']
|
||||
},
|
||||
writerOpts: {
|
||||
commitsSort: ['subject', 'scope'],
|
||||
mainTemplate: templates.main.text,
|
||||
headerPartial: templates.header.text,
|
||||
commitPartial: templates.commit.text,
|
||||
footerPartial: templates.footer.text,
|
||||
},
|
||||
}],
|
||||
['@semantic-release/changelog', {
|
||||
'changelogFile': changelogFile,
|
||||
}],
|
||||
['@semantic-release/npm', {
|
||||
'npmPublish': true,
|
||||
}],
|
||||
['@semantic-release/git', {
|
||||
assets: [changelogFile, 'package.json', 'package-lock.json', 'npm-shrinkwrap.json'],
|
||||
}],
|
||||
[
|
||||
"@saithodev/semantic-release-backmerge",
|
||||
{
|
||||
"branches": [
|
||||
{ from: "beta", to: "alpha" },
|
||||
{ from: "release", to: "beta" },
|
||||
{ from: "release", to: "alpha" },
|
||||
]
|
||||
}
|
||||
],
|
||||
['@semantic-release/github', {
|
||||
successComment: getReleaseComment(),
|
||||
labels: ['type:ci'],
|
||||
releasedLabels: ['state:released<%= nextRelease.channel ? `-\${nextRelease.channel}` : "" %>']
|
||||
}],
|
||||
],
|
||||
};
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
async function loadTemplates() {
|
||||
for (const template of Object.keys(templates)) {
|
||||
const text = await readFile(path.resolve(__dirname, resourcePath, templates[template].file));
|
||||
templates[template].text = text;
|
||||
}
|
||||
}
|
||||
|
||||
async function readFile(filePath) {
|
||||
return await fs.readFile(filePath, 'utf-8');
|
||||
}
|
||||
|
||||
function getReleaseComment() {
|
||||
const url = repositoryUrl + '/releases/tag/${nextRelease.gitTag}';
|
||||
const comment = '🎉 This change has been released in version [${nextRelease.version}](' + url + ')';
|
||||
return comment;
|
||||
}
|
||||
|
||||
module.exports = config();
|
||||
+5
-5
@@ -1,11 +1,11 @@
|
||||
#!/bin/sh -e
|
||||
set -x
|
||||
if [ "${GITHUB_ACTIONS}" = "" ];
|
||||
if [ "${TRAVIS_REPO_SLUG}" = "" ];
|
||||
then
|
||||
echo "Cannot release docs without GITHUB_ACTIONS set"
|
||||
echo "Cannot release docs without TRAVIS_REPO_SLUG set"
|
||||
exit 0;
|
||||
fi
|
||||
REPO="https://github.com/parse-community/parse-server"
|
||||
REPO="https://github.com/${TRAVIS_REPO_SLUG}"
|
||||
|
||||
rm -rf docs
|
||||
git clone -b gh-pages --single-branch $REPO ./docs
|
||||
@@ -15,9 +15,9 @@ cd ..
|
||||
|
||||
DEST="master"
|
||||
|
||||
if [ "${SOURCE_TAG}" != "" ];
|
||||
if [ "${TRAVIS_TAG}" != "" ];
|
||||
then
|
||||
DEST="${SOURCE_TAG}"
|
||||
DEST="${TRAVIS_TAG}"
|
||||
# change the default page to the latest
|
||||
echo "<meta http-equiv='refresh' content='0; url=/parse-server/api/${DEST}'>" > "docs/api/index.html"
|
||||
fi
|
||||
|
||||
@@ -11,39 +11,6 @@
|
||||
*/
|
||||
const parsers = require('../src/Options/parsers');
|
||||
|
||||
/** The types of nested options. */
|
||||
const nestedOptionTypes = [
|
||||
'CustomPagesOptions',
|
||||
'DatabaseOptions',
|
||||
'FileUploadOptions',
|
||||
'IdempotencyOptions',
|
||||
'Object',
|
||||
'PagesCustomUrlsOptions',
|
||||
'PagesOptions',
|
||||
'PagesRoute',
|
||||
'PasswordPolicyOptions',
|
||||
'SecurityOptions',
|
||||
'SchemaOptions',
|
||||
];
|
||||
|
||||
/** The prefix of environment variables for nested options. */
|
||||
const nestedOptionEnvPrefix = {
|
||||
'AccountLockoutOptions': 'PARSE_SERVER_ACCOUNT_LOCKOUT_',
|
||||
'CustomPagesOptions': 'PARSE_SERVER_CUSTOM_PAGES_',
|
||||
'DatabaseOptions': 'PARSE_SERVER_DATABASE_',
|
||||
'FileUploadOptions': 'PARSE_SERVER_FILE_UPLOAD_',
|
||||
'IdempotencyOptions': 'PARSE_SERVER_EXPERIMENTAL_IDEMPOTENCY_',
|
||||
'LiveQueryOptions': 'PARSE_SERVER_LIVEQUERY_',
|
||||
'LiveQueryServerOptions': 'PARSE_LIVE_QUERY_SERVER_',
|
||||
'PagesCustomUrlsOptions': 'PARSE_SERVER_PAGES_CUSTOM_URL_',
|
||||
'PagesOptions': 'PARSE_SERVER_PAGES_',
|
||||
'PagesRoute': 'PARSE_SERVER_PAGES_ROUTE_',
|
||||
'ParseServerOptions': 'PARSE_SERVER_',
|
||||
'PasswordPolicyOptions': 'PARSE_SERVER_PASSWORD_POLICY_',
|
||||
'SecurityOptions': 'PARSE_SERVER_SECURITY_',
|
||||
'SchemaOptions': 'PARSE_SERVER_SCHEMA_',
|
||||
};
|
||||
|
||||
function last(array) {
|
||||
return array[array.length - 1];
|
||||
}
|
||||
@@ -52,7 +19,7 @@ const letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
||||
function toENV(key) {
|
||||
let str = '';
|
||||
let previousIsUpper = false;
|
||||
for (let i = 0; i < key.length; i++) {
|
||||
for(let i = 0; i < key.length; i++) {
|
||||
const char = key[i];
|
||||
if (letters.indexOf(char) >= 0) {
|
||||
if (!previousIsUpper) {
|
||||
@@ -73,8 +40,17 @@ function getCommentValue(comment) {
|
||||
}
|
||||
|
||||
function getENVPrefix(iface) {
|
||||
if (nestedOptionEnvPrefix[iface.id.name]) {
|
||||
return nestedOptionEnvPrefix[iface.id.name]
|
||||
if (iface.id.name === 'ParseServerOptions') {
|
||||
return 'PARSE_SERVER_';
|
||||
}
|
||||
if (iface.id.name === 'CustomPagesOptions') {
|
||||
return 'PARSE_SERVER_CUSTOM_PAGES_';
|
||||
}
|
||||
if (iface.id.name === 'LiveQueryServerOptions') {
|
||||
return 'PARSE_LIVE_QUERY_SERVER_';
|
||||
}
|
||||
if (iface.id.name === 'LiveQueryOptions') {
|
||||
return 'PARSE_SERVER_LIVEQUERY_';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,20 +150,6 @@ function parseDefaultValue(elt, value, t) {
|
||||
literalValue = t.arrayExpression(array.map((value) => {
|
||||
if (typeof value == 'string') {
|
||||
return t.stringLiteral(value);
|
||||
} else if (typeof value == 'number') {
|
||||
return t.numericLiteral(value);
|
||||
} else if (typeof value == 'object') {
|
||||
const object = parsers.objectParser(value);
|
||||
const props = Object.entries(object).map(([k, v]) => {
|
||||
if (typeof v == 'string') {
|
||||
return t.objectProperty(t.identifier(k), t.stringLiteral(v));
|
||||
} else if (typeof v == 'number') {
|
||||
return t.objectProperty(t.identifier(k), t.numericLiteral(v));
|
||||
} else if (typeof v == 'boolean') {
|
||||
return t.objectProperty(t.identifier(k), t.booleanLiteral(v));
|
||||
}
|
||||
});
|
||||
return t.objectExpression(props);
|
||||
} else {
|
||||
throw new Error('Unable to parse array');
|
||||
}
|
||||
@@ -201,8 +163,7 @@ function parseDefaultValue(elt, value, t) {
|
||||
if (type == 'NumberOrBoolean') {
|
||||
literalValue = t.numericLiteral(parsers.numberOrBoolParser('')(value));
|
||||
}
|
||||
|
||||
if (nestedOptionTypes.includes(type)) {
|
||||
if (type == 'CustomPagesOptions') {
|
||||
const object = parsers.objectParser(value);
|
||||
const props = Object.keys(object).map((key) => {
|
||||
return t.objectProperty(key, object[value]);
|
||||
@@ -252,16 +213,11 @@ function inject(t, list) {
|
||||
type = elt.typeAnnotation.id.name;
|
||||
}
|
||||
if (type === 'Array') {
|
||||
type = elt.typeAnnotation.elementType.id
|
||||
? `${elt.typeAnnotation.elementType.id.name}[]`
|
||||
: `${elt.typeAnnotation.elementType.type.replace('TypeAnnotation', '')}[]`;
|
||||
type = `${elt.typeAnnotation.elementType.type.replace('TypeAnnotation', '')}[]`;
|
||||
}
|
||||
if (type === 'NumberOrBoolean') {
|
||||
type = 'Number|Boolean';
|
||||
}
|
||||
if (type === 'NumberOrString') {
|
||||
type = 'Number|String';
|
||||
}
|
||||
if (type === 'Adapter') {
|
||||
const adapterType = elt.typeAnnotation.typeParameters.params[0].id.name;
|
||||
type = `Adapter<${adapterType}>`;
|
||||
@@ -275,8 +231,8 @@ function inject(t, list) {
|
||||
return { results, comments };
|
||||
}
|
||||
|
||||
const makeRequire = function (variableName, module, t) {
|
||||
const decl = t.variableDeclarator(t.identifier(variableName), t.callExpression(t.identifier('require'), [t.stringLiteral(module)]));
|
||||
const makeRequire = function(variableName, module, t) {
|
||||
const decl = t.variableDeclarator(t.identifier(variableName), t.callExpression(t.identifier('require'), [t.stringLiteral(module)]));
|
||||
return t.variableDeclaration('var', [decl])
|
||||
}
|
||||
let docs = ``;
|
||||
@@ -285,14 +241,14 @@ const plugin = function (babel) {
|
||||
const moduleExports = t.memberExpression(t.identifier('module'), t.identifier('exports'));
|
||||
return {
|
||||
visitor: {
|
||||
ImportDeclaration: function (path) {
|
||||
ImportDeclaration: function(path) {
|
||||
path.remove();
|
||||
},
|
||||
Program: function (path) {
|
||||
Program: function(path) {
|
||||
// Inject the parser's loader
|
||||
path.unshiftContainer('body', makeRequire('parsers', './parsers', t));
|
||||
},
|
||||
ExportDeclaration: function (path) {
|
||||
ExportDeclaration: function(path) {
|
||||
// Export declaration on an interface
|
||||
if (path.node && path.node.declaration && path.node.declaration.type == 'InterfaceDeclaration') {
|
||||
const { results, comments } = inject(t, doInterface(path.node.declaration));
|
||||
@@ -315,6 +271,6 @@ Do not edit manually, but update Options/index.js
|
||||
`
|
||||
|
||||
const babel = require("@babel/core");
|
||||
const res = babel.transformFileSync('./src/Options/index.js', { plugins: [plugin, '@babel/transform-flow-strip-types'], babelrc: false, auxiliaryCommentBefore, sourceMaps: false });
|
||||
const res = babel.transformFileSync('./src/Options/index.js', { plugins: [ plugin, '@babel/transform-flow-strip-types' ], babelrc: false, auxiliaryCommentBefore, sourceMaps: false });
|
||||
require('fs').writeFileSync('./src/Options/Definitions.js', res.code + '\n');
|
||||
require('fs').writeFileSync('./src/Options/docs.js', docs);
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
echo "[SCRIPT] Before Script :: Setup Parse DB for Postgres"
|
||||
|
||||
PGPASSWORD=postgres psql -v ON_ERROR_STOP=1 -h localhost -U postgres <<-EOSQL
|
||||
CREATE DATABASE parse_server_postgres_adapter_test_database;
|
||||
\c parse_server_postgres_adapter_test_database;
|
||||
CREATE EXTENSION pgcrypto;
|
||||
CREATE EXTENSION postgis;
|
||||
CREATE EXTENSION postgis_topology;
|
||||
EOSQL
|
||||
@@ -1,30 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
echo "[SCRIPT] Before Script :: Setup Parse Postgres configuration file"
|
||||
|
||||
# DB Version: 13
|
||||
# OS Type: linux
|
||||
# DB Type: web
|
||||
# Total Memory (RAM): 6 GB
|
||||
# CPUs num: 1
|
||||
# Data Storage: ssd
|
||||
|
||||
PGPASSWORD=postgres psql -v ON_ERROR_STOP=1 -h localhost -U postgres <<-EOSQL
|
||||
ALTER SYSTEM SET max_connections TO '200';
|
||||
ALTER SYSTEM SET shared_buffers TO '1536MB';
|
||||
ALTER SYSTEM SET effective_cache_size TO '4608MB';
|
||||
ALTER SYSTEM SET maintenance_work_mem TO '384MB';
|
||||
ALTER SYSTEM SET checkpoint_completion_target TO '0.9';
|
||||
ALTER SYSTEM SET wal_buffers TO '16MB';
|
||||
ALTER SYSTEM SET default_statistics_target TO '100';
|
||||
ALTER SYSTEM SET random_page_cost TO '1.1';
|
||||
ALTER SYSTEM SET effective_io_concurrency TO '200';
|
||||
ALTER SYSTEM SET work_mem TO '3932kB';
|
||||
ALTER SYSTEM SET min_wal_size TO '1GB';
|
||||
ALTER SYSTEM SET max_wal_size TO '4GB';
|
||||
SELECT pg_reload_conf();
|
||||
EOSQL
|
||||
|
||||
exec "$@"
|
||||
+1
-12
@@ -13,18 +13,8 @@
|
||||
"Item": true,
|
||||
"Container": true,
|
||||
"equal": true,
|
||||
"expectAsync": true,
|
||||
"notEqual": true,
|
||||
"it_only_db": true,
|
||||
"it_only_mongodb_version": true,
|
||||
"it_only_postgres_version": true,
|
||||
"it_only_node_version": true,
|
||||
"fit_only_mongodb_version": true,
|
||||
"fit_only_node_version": true,
|
||||
"it_exclude_mongodb_version": true,
|
||||
"it_exclude_postgres_version": true,
|
||||
"fit_exclude_mongodb_version": true,
|
||||
"fit_exclude_node_version": true,
|
||||
"it_exclude_dbs": true,
|
||||
"describe_only_db": true,
|
||||
"describe_only": true,
|
||||
@@ -33,8 +23,7 @@
|
||||
"range": true,
|
||||
"jequal": true,
|
||||
"create": true,
|
||||
"arrayContains": true,
|
||||
"databaseAdapter": true
|
||||
"arrayContains": true
|
||||
},
|
||||
"rules": {
|
||||
"no-console": [0],
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
'use strict';
|
||||
|
||||
const Config = require('../lib/Config');
|
||||
const Definitions = require('../lib/Options/Definitions');
|
||||
const request = require('../lib/request');
|
||||
|
||||
const loginWithWrongCredentialsShouldFail = function (username, password) {
|
||||
const loginWithWrongCredentialsShouldFail = function(username, password) {
|
||||
return new Promise((resolve, reject) => {
|
||||
Parse.User.logIn(username, password)
|
||||
.then(() => reject('login should have failed'))
|
||||
@@ -18,7 +16,7 @@ const loginWithWrongCredentialsShouldFail = function (username, password) {
|
||||
});
|
||||
};
|
||||
|
||||
const isAccountLockoutError = function (username, password, duration, waitTime) {
|
||||
const isAccountLockoutError = function(username, password, duration, waitTime) {
|
||||
return new Promise((resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
Parse.User.logIn(username, password)
|
||||
@@ -52,17 +50,28 @@ describe('Account Lockout Policy: ', () => {
|
||||
return user.signUp(null);
|
||||
})
|
||||
.then(() => {
|
||||
return loginWithWrongCredentialsShouldFail('username1', 'incorrect password 1');
|
||||
return loginWithWrongCredentialsShouldFail(
|
||||
'username1',
|
||||
'incorrect password 1'
|
||||
);
|
||||
})
|
||||
.then(() => {
|
||||
return loginWithWrongCredentialsShouldFail('username1', 'incorrect password 2');
|
||||
return loginWithWrongCredentialsShouldFail(
|
||||
'username1',
|
||||
'incorrect password 2'
|
||||
);
|
||||
})
|
||||
.then(() => {
|
||||
return loginWithWrongCredentialsShouldFail('username1', 'incorrect password 3');
|
||||
return loginWithWrongCredentialsShouldFail(
|
||||
'username1',
|
||||
'incorrect password 3'
|
||||
);
|
||||
})
|
||||
.then(() => done())
|
||||
.catch(err => {
|
||||
fail('allow unlimited failed login attempts failed: ' + JSON.stringify(err));
|
||||
fail(
|
||||
'allow unlimited failed login attempts failed: ' + JSON.stringify(err)
|
||||
);
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -84,11 +93,15 @@ describe('Account Lockout Policy: ', () => {
|
||||
.catch(err => {
|
||||
if (
|
||||
err &&
|
||||
err === 'Account lockout duration should be greater than 0 and less than 100000'
|
||||
err ===
|
||||
'Account lockout duration should be greater than 0 and less than 100000'
|
||||
) {
|
||||
done();
|
||||
} else {
|
||||
fail('set duration to an invalid number test failed: ' + JSON.stringify(err));
|
||||
fail(
|
||||
'set duration to an invalid number test failed: ' +
|
||||
JSON.stringify(err)
|
||||
);
|
||||
done();
|
||||
}
|
||||
});
|
||||
@@ -111,11 +124,15 @@ describe('Account Lockout Policy: ', () => {
|
||||
.catch(err => {
|
||||
if (
|
||||
err &&
|
||||
err === 'Account lockout threshold should be an integer greater than 0 and less than 1000'
|
||||
err ===
|
||||
'Account lockout threshold should be an integer greater than 0 and less than 1000'
|
||||
) {
|
||||
done();
|
||||
} else {
|
||||
fail('set threshold to an invalid number test failed: ' + JSON.stringify(err));
|
||||
fail(
|
||||
'set threshold to an invalid number test failed: ' +
|
||||
JSON.stringify(err)
|
||||
);
|
||||
done();
|
||||
}
|
||||
});
|
||||
@@ -138,11 +155,14 @@ describe('Account Lockout Policy: ', () => {
|
||||
.catch(err => {
|
||||
if (
|
||||
err &&
|
||||
err === 'Account lockout threshold should be an integer greater than 0 and less than 1000'
|
||||
err ===
|
||||
'Account lockout threshold should be an integer greater than 0 and less than 1000'
|
||||
) {
|
||||
done();
|
||||
} else {
|
||||
fail('threshold value < 1 is invalid test failed: ' + JSON.stringify(err));
|
||||
fail(
|
||||
'threshold value < 1 is invalid test failed: ' + JSON.stringify(err)
|
||||
);
|
||||
done();
|
||||
}
|
||||
});
|
||||
@@ -165,11 +185,15 @@ describe('Account Lockout Policy: ', () => {
|
||||
.catch(err => {
|
||||
if (
|
||||
err &&
|
||||
err === 'Account lockout threshold should be an integer greater than 0 and less than 1000'
|
||||
err ===
|
||||
'Account lockout threshold should be an integer greater than 0 and less than 1000'
|
||||
) {
|
||||
done();
|
||||
} else {
|
||||
fail('threshold value > 999 is invalid test failed: ' + JSON.stringify(err));
|
||||
fail(
|
||||
'threshold value > 999 is invalid test failed: ' +
|
||||
JSON.stringify(err)
|
||||
);
|
||||
done();
|
||||
}
|
||||
});
|
||||
@@ -192,11 +216,14 @@ describe('Account Lockout Policy: ', () => {
|
||||
.catch(err => {
|
||||
if (
|
||||
err &&
|
||||
err === 'Account lockout duration should be greater than 0 and less than 100000'
|
||||
err ===
|
||||
'Account lockout duration should be greater than 0 and less than 100000'
|
||||
) {
|
||||
done();
|
||||
} else {
|
||||
fail('duration value < 1 is invalid test failed: ' + JSON.stringify(err));
|
||||
fail(
|
||||
'duration value < 1 is invalid test failed: ' + JSON.stringify(err)
|
||||
);
|
||||
done();
|
||||
}
|
||||
});
|
||||
@@ -219,11 +246,15 @@ describe('Account Lockout Policy: ', () => {
|
||||
.catch(err => {
|
||||
if (
|
||||
err &&
|
||||
err === 'Account lockout duration should be greater than 0 and less than 100000'
|
||||
err ===
|
||||
'Account lockout duration should be greater than 0 and less than 100000'
|
||||
) {
|
||||
done();
|
||||
} else {
|
||||
fail('duration value > 99999 is invalid test failed: ' + JSON.stringify(err));
|
||||
fail(
|
||||
'duration value > 99999 is invalid test failed: ' +
|
||||
JSON.stringify(err)
|
||||
);
|
||||
done();
|
||||
}
|
||||
});
|
||||
@@ -245,10 +276,16 @@ describe('Account Lockout Policy: ', () => {
|
||||
return user.signUp();
|
||||
})
|
||||
.then(() => {
|
||||
return loginWithWrongCredentialsShouldFail('username2', 'wrong password');
|
||||
return loginWithWrongCredentialsShouldFail(
|
||||
'username2',
|
||||
'wrong password'
|
||||
);
|
||||
})
|
||||
.then(() => {
|
||||
return loginWithWrongCredentialsShouldFail('username2', 'wrong password');
|
||||
return loginWithWrongCredentialsShouldFail(
|
||||
'username2',
|
||||
'wrong password'
|
||||
);
|
||||
})
|
||||
.then(() => {
|
||||
return isAccountLockoutError('username2', 'wrong password', 1, 1);
|
||||
@@ -257,7 +294,10 @@ describe('Account Lockout Policy: ', () => {
|
||||
done();
|
||||
})
|
||||
.catch(err => {
|
||||
fail('lock account after failed login attempts test failed: ' + JSON.stringify(err));
|
||||
fail(
|
||||
'lock account after failed login attempts test failed: ' +
|
||||
JSON.stringify(err)
|
||||
);
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -278,10 +318,16 @@ describe('Account Lockout Policy: ', () => {
|
||||
return user.signUp();
|
||||
})
|
||||
.then(() => {
|
||||
return loginWithWrongCredentialsShouldFail('username3', 'wrong password');
|
||||
return loginWithWrongCredentialsShouldFail(
|
||||
'username3',
|
||||
'wrong password'
|
||||
);
|
||||
})
|
||||
.then(() => {
|
||||
return loginWithWrongCredentialsShouldFail('username3', 'wrong password');
|
||||
return loginWithWrongCredentialsShouldFail(
|
||||
'username3',
|
||||
'wrong password'
|
||||
);
|
||||
})
|
||||
.then(() => {
|
||||
return isAccountLockoutError('username3', 'wrong password', 0.05, 1);
|
||||
@@ -294,7 +340,10 @@ describe('Account Lockout Policy: ', () => {
|
||||
done();
|
||||
})
|
||||
.catch(err => {
|
||||
fail('account should be locked for duration mins test failed: ' + JSON.stringify(err));
|
||||
fail(
|
||||
'account should be locked for duration mins test failed: ' +
|
||||
JSON.stringify(err)
|
||||
);
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -315,10 +364,16 @@ describe('Account Lockout Policy: ', () => {
|
||||
return user.signUp();
|
||||
})
|
||||
.then(() => {
|
||||
return loginWithWrongCredentialsShouldFail('username4', 'wrong password');
|
||||
return loginWithWrongCredentialsShouldFail(
|
||||
'username4',
|
||||
'wrong password'
|
||||
);
|
||||
})
|
||||
.then(() => {
|
||||
return loginWithWrongCredentialsShouldFail('username4', 'wrong password');
|
||||
return loginWithWrongCredentialsShouldFail(
|
||||
'username4',
|
||||
'wrong password'
|
||||
);
|
||||
})
|
||||
.then(() => {
|
||||
// allow locked user to login after 3 seconds with a valid userid and password
|
||||
@@ -342,125 +397,3 @@ describe('Account Lockout Policy: ', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('lockout with password reset option', () => {
|
||||
let sendPasswordResetEmail;
|
||||
|
||||
async function setup(options = {}) {
|
||||
const accountLockout = Object.assign(
|
||||
{
|
||||
duration: 10000,
|
||||
threshold: 1,
|
||||
},
|
||||
options
|
||||
);
|
||||
const config = {
|
||||
appName: 'exampleApp',
|
||||
accountLockout: accountLockout,
|
||||
publicServerURL: 'http://localhost:8378/1',
|
||||
emailAdapter: {
|
||||
sendVerificationEmail: () => Promise.resolve(),
|
||||
sendPasswordResetEmail: () => Promise.resolve(),
|
||||
sendMail: () => {},
|
||||
},
|
||||
};
|
||||
await reconfigureServer(config);
|
||||
|
||||
sendPasswordResetEmail = spyOn(config.emailAdapter, 'sendPasswordResetEmail').and.callThrough();
|
||||
}
|
||||
|
||||
it('accepts valid unlockOnPasswordReset option', async () => {
|
||||
const values = [true, false];
|
||||
|
||||
for (const value of values) {
|
||||
await expectAsync(setup({ unlockOnPasswordReset: value })).toBeResolved();
|
||||
}
|
||||
});
|
||||
|
||||
it('rejects invalid unlockOnPasswordReset option', async () => {
|
||||
const values = ['a', 0, {}, [], null];
|
||||
|
||||
for (const value of values) {
|
||||
await expectAsync(setup({ unlockOnPasswordReset: value })).toBeRejected();
|
||||
}
|
||||
});
|
||||
|
||||
it('uses default value if unlockOnPasswordReset is not set', async () => {
|
||||
await expectAsync(setup({ unlockOnPasswordReset: undefined })).toBeResolved();
|
||||
|
||||
const parseConfig = Config.get(Parse.applicationId);
|
||||
expect(parseConfig.accountLockout.unlockOnPasswordReset).toBe(
|
||||
Definitions.AccountLockoutOptions.unlockOnPasswordReset.default
|
||||
);
|
||||
});
|
||||
|
||||
it('allow login for locked account after password reset', async () => {
|
||||
await setup({ unlockOnPasswordReset: true });
|
||||
const config = Config.get(Parse.applicationId);
|
||||
|
||||
const user = new Parse.User();
|
||||
const username = 'exampleUsername';
|
||||
const password = 'examplePassword';
|
||||
user.setUsername(username);
|
||||
user.setPassword(password);
|
||||
user.setEmail('mail@example.com');
|
||||
await user.signUp();
|
||||
|
||||
await expectAsync(Parse.User.logIn(username, 'incorrectPassword')).toBeRejected();
|
||||
await expectAsync(Parse.User.logIn(username, password)).toBeRejected();
|
||||
|
||||
await Parse.User.requestPasswordReset(user.getEmail());
|
||||
await expectAsync(Parse.User.logIn(username, password)).toBeRejected();
|
||||
|
||||
const link = sendPasswordResetEmail.calls.all()[0].args[0].link;
|
||||
const linkUrl = new URL(link);
|
||||
const token = linkUrl.searchParams.get('token');
|
||||
const newPassword = 'newPassword';
|
||||
await request({
|
||||
method: 'POST',
|
||||
url: `${config.publicServerURL}/apps/test/request_password_reset`,
|
||||
body: `new_password=${newPassword}&token=${token}&username=${username}`,
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
followRedirects: false,
|
||||
});
|
||||
|
||||
await expectAsync(Parse.User.logIn(username, newPassword)).toBeResolved();
|
||||
});
|
||||
|
||||
it('reject login for locked account after password reset (default)', async () => {
|
||||
await setup();
|
||||
const config = Config.get(Parse.applicationId);
|
||||
|
||||
const user = new Parse.User();
|
||||
const username = 'exampleUsername';
|
||||
const password = 'examplePassword';
|
||||
user.setUsername(username);
|
||||
user.setPassword(password);
|
||||
user.setEmail('mail@example.com');
|
||||
await user.signUp();
|
||||
|
||||
await expectAsync(Parse.User.logIn(username, 'incorrectPassword')).toBeRejected();
|
||||
await expectAsync(Parse.User.logIn(username, password)).toBeRejected();
|
||||
|
||||
await Parse.User.requestPasswordReset(user.getEmail());
|
||||
await expectAsync(Parse.User.logIn(username, password)).toBeRejected();
|
||||
|
||||
const link = sendPasswordResetEmail.calls.all()[0].args[0].link;
|
||||
const linkUrl = new URL(link);
|
||||
const token = linkUrl.searchParams.get('token');
|
||||
const newPassword = 'newPassword';
|
||||
await request({
|
||||
method: 'POST',
|
||||
url: `${config.publicServerURL}/apps/test/request_password_reset`,
|
||||
body: `new_password=${newPassword}&token=${token}&username=${username}`,
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
followRedirects: false,
|
||||
});
|
||||
|
||||
await expectAsync(Parse.User.logIn(username, newPassword)).toBeRejected();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
const AdaptableController = require('../lib/Controllers/AdaptableController').AdaptableController;
|
||||
const AdaptableController = require('../lib/Controllers/AdaptableController')
|
||||
.AdaptableController;
|
||||
const FilesAdapter = require('../lib/Adapters/Files/FilesAdapter').default;
|
||||
const FilesController = require('../lib/Controllers/FilesController').FilesController;
|
||||
const FilesController = require('../lib/Controllers/FilesController')
|
||||
.FilesController;
|
||||
|
||||
const MockController = function (options) {
|
||||
const MockController = function(options) {
|
||||
AdaptableController.call(this, options);
|
||||
};
|
||||
MockController.prototype = Object.create(AdaptableController.prototype);
|
||||
@@ -58,11 +60,10 @@ describe('AdaptableController', () => {
|
||||
|
||||
it('should accept an object adapter', done => {
|
||||
const adapter = {
|
||||
createFile: function () {},
|
||||
deleteFile: function () {},
|
||||
getFileData: function () {},
|
||||
getFileLocation: function () {},
|
||||
validateFilename: function () {},
|
||||
createFile: function() {},
|
||||
deleteFile: function() {},
|
||||
getFileData: function() {},
|
||||
getFileLocation: function() {},
|
||||
};
|
||||
expect(() => {
|
||||
new FilesController(adapter);
|
||||
@@ -72,11 +73,10 @@ describe('AdaptableController', () => {
|
||||
|
||||
it('should accept an prototype based object adapter', done => {
|
||||
function AGoodAdapter() {}
|
||||
AGoodAdapter.prototype.createFile = function () {};
|
||||
AGoodAdapter.prototype.deleteFile = function () {};
|
||||
AGoodAdapter.prototype.getFileData = function () {};
|
||||
AGoodAdapter.prototype.getFileLocation = function () {};
|
||||
AGoodAdapter.prototype.validateFilename = function () {};
|
||||
AGoodAdapter.prototype.createFile = function() {};
|
||||
AGoodAdapter.prototype.deleteFile = function() {};
|
||||
AGoodAdapter.prototype.getFileData = function() {};
|
||||
AGoodAdapter.prototype.getFileLocation = function() {};
|
||||
|
||||
const adapter = new AGoodAdapter();
|
||||
expect(() => {
|
||||
|
||||
+13
-12
@@ -1,12 +1,12 @@
|
||||
const loadAdapter = require('../lib/Adapters/AdapterLoader').loadAdapter;
|
||||
const FilesAdapter = require('@parse/fs-files-adapter').default;
|
||||
const S3Adapter = require('@parse/s3-files-adapter').default;
|
||||
const ParsePushAdapter = require('@parse/push-adapter').default;
|
||||
const MockFilesAdapter = require('mock-files-adapter');
|
||||
const Config = require('../lib/Config');
|
||||
|
||||
describe('AdapterLoader', () => {
|
||||
it('should instantiate an adapter from string in object', done => {
|
||||
const adapterPath = require('path').resolve('./spec/support/MockAdapter');
|
||||
const adapterPath = require('path').resolve('./spec/MockAdapter');
|
||||
|
||||
const adapter = loadAdapter({
|
||||
adapter: adapterPath,
|
||||
@@ -23,7 +23,7 @@ describe('AdapterLoader', () => {
|
||||
});
|
||||
|
||||
it('should instantiate an adapter from string', done => {
|
||||
const adapterPath = require('path').resolve('./spec/support/MockAdapter');
|
||||
const adapterPath = require('path').resolve('./spec/MockAdapter');
|
||||
const adapter = loadAdapter(adapterPath);
|
||||
|
||||
expect(adapter instanceof Object).toBe(true);
|
||||
@@ -31,7 +31,9 @@ describe('AdapterLoader', () => {
|
||||
});
|
||||
|
||||
it('should instantiate an adapter from string that is module', done => {
|
||||
const adapterPath = require('path').resolve('./lib/Adapters/Files/FilesAdapter');
|
||||
const adapterPath = require('path').resolve(
|
||||
'./lib/Adapters/Files/FilesAdapter'
|
||||
);
|
||||
const adapter = loadAdapter({
|
||||
adapter: adapterPath,
|
||||
});
|
||||
@@ -86,14 +88,14 @@ describe('AdapterLoader', () => {
|
||||
});
|
||||
|
||||
it('should fail loading an improperly configured adapter', done => {
|
||||
const Adapter = function (options) {
|
||||
const Adapter = function(options) {
|
||||
if (!options.foo) {
|
||||
throw 'foo is required for that adapter';
|
||||
}
|
||||
};
|
||||
const adapterOptions = {
|
||||
param: 'key',
|
||||
doSomething: function () {},
|
||||
doSomething: function() {},
|
||||
};
|
||||
|
||||
expect(() => {
|
||||
@@ -119,7 +121,7 @@ describe('AdapterLoader', () => {
|
||||
});
|
||||
|
||||
it('should load custom push adapter from string (#3544)', done => {
|
||||
const adapterPath = require('path').resolve('./spec/support/MockPushAdapter');
|
||||
const adapterPath = require('path').resolve('./spec/MockPushAdapter');
|
||||
const options = {
|
||||
ios: {
|
||||
bundleId: 'bundle.id',
|
||||
@@ -142,12 +144,11 @@ describe('AdapterLoader', () => {
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
it('should load file adapter from direct passing', done => {
|
||||
spyOn(console, 'warn').and.callFake(() => {});
|
||||
const mockFilesAdapter = new MockFilesAdapter('key', 'secret', 'bucket');
|
||||
it('should load S3Adapter from direct passing', done => {
|
||||
const s3Adapter = new S3Adapter('key', 'secret', 'bucket');
|
||||
expect(() => {
|
||||
const adapter = loadAdapter(mockFilesAdapter, FilesAdapter);
|
||||
expect(adapter).toBe(mockFilesAdapter);
|
||||
const adapter = loadAdapter(s3Adapter, FilesAdapter);
|
||||
expect(adapter).toBe(s3Adapter);
|
||||
}).not.toThrow();
|
||||
done();
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const AggregateRouter = require('../lib/Routers/AggregateRouter').AggregateRouter;
|
||||
const AggregateRouter = require('../lib/Routers/AggregateRouter')
|
||||
.AggregateRouter;
|
||||
|
||||
describe('AggregateRouter', () => {
|
||||
// TODO: update pipeline syntax. See [#7339](https://bit.ly/3incnWx)
|
||||
it('get pipeline from Array', () => {
|
||||
const body = [
|
||||
{
|
||||
@@ -13,7 +13,6 @@ describe('AggregateRouter', () => {
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
|
||||
// TODO: update pipeline syntax. See [#7339](https://bit.ly/3incnWx)
|
||||
it('get pipeline from Object', () => {
|
||||
const body = {
|
||||
group: { objectId: {} },
|
||||
@@ -23,7 +22,6 @@ describe('AggregateRouter', () => {
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
|
||||
// TODO: update pipeline syntax. See [#7339](https://bit.ly/3incnWx)
|
||||
it('get pipeline from Pipeline Operator (Array)', () => {
|
||||
const body = {
|
||||
pipeline: [
|
||||
@@ -37,7 +35,6 @@ describe('AggregateRouter', () => {
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
|
||||
// TODO: update pipeline syntax. See [#7339](https://bit.ly/3incnWx)
|
||||
it('get pipeline from Pipeline Operator (Object)', () => {
|
||||
const body = {
|
||||
pipeline: {
|
||||
@@ -49,7 +46,6 @@ describe('AggregateRouter', () => {
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
|
||||
// TODO: update pipeline syntax. See [#7339](https://bit.ly/3incnWx)
|
||||
it('get pipeline fails multiple keys in Array stage ', () => {
|
||||
const body = [
|
||||
{
|
||||
@@ -60,11 +56,12 @@ describe('AggregateRouter', () => {
|
||||
try {
|
||||
AggregateRouter.getPipeline(body);
|
||||
} catch (e) {
|
||||
expect(e.message).toBe('Pipeline stages should only have one key found group, match');
|
||||
expect(e.message).toBe(
|
||||
'Pipeline stages should only have one key found group, match'
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// TODO: update pipeline syntax. See [#7339](https://bit.ly/3incnWx)
|
||||
it('get pipeline fails multiple keys in Pipeline Operator Array stage ', () => {
|
||||
const body = {
|
||||
pipeline: [
|
||||
@@ -77,88 +74,9 @@ describe('AggregateRouter', () => {
|
||||
try {
|
||||
AggregateRouter.getPipeline(body);
|
||||
} catch (e) {
|
||||
expect(e.message).toBe('Pipeline stages should only have one key found group, match');
|
||||
expect(e.message).toBe(
|
||||
'Pipeline stages should only have one key found group, match'
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// TODO: update pipeline syntax. See [#7339](https://bit.ly/3incnWx)
|
||||
it('get search pipeline from Pipeline Operator (Array)', () => {
|
||||
const body = {
|
||||
pipeline: {
|
||||
search: {},
|
||||
},
|
||||
};
|
||||
const expected = [{ $search: {} }];
|
||||
const result = AggregateRouter.getPipeline(body);
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
|
||||
it('support stage name starting with `$`', () => {
|
||||
const body = {
|
||||
$match: { someKey: 'whatever' },
|
||||
};
|
||||
const expected = [{ $match: { someKey: 'whatever' } }];
|
||||
const result = AggregateRouter.getPipeline(body);
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
|
||||
it('support nested stage names starting with `$`', () => {
|
||||
const body = [
|
||||
{
|
||||
lookup: {
|
||||
from: 'ACollection',
|
||||
let: { id: '_id' },
|
||||
as: 'results',
|
||||
pipeline: [
|
||||
{
|
||||
$match: {
|
||||
$expr: {
|
||||
$eq: ['$_id', '$$id'],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
];
|
||||
const expected = [
|
||||
{
|
||||
$lookup: {
|
||||
from: 'ACollection',
|
||||
let: { id: '_id' },
|
||||
as: 'results',
|
||||
pipeline: [
|
||||
{
|
||||
$match: {
|
||||
$expr: {
|
||||
$eq: ['$_id', '$$id'],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
];
|
||||
const result = AggregateRouter.getPipeline(body);
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
|
||||
it('support the use of `_id` in stages', () => {
|
||||
const body = [
|
||||
{ match: { _id: 'randomId' } },
|
||||
{ sort: { _id: -1 } },
|
||||
{ addFields: { _id: 1 } },
|
||||
{ group: { _id: {} } },
|
||||
{ project: { _id: 0 } },
|
||||
];
|
||||
const expected = [
|
||||
{ $match: { _id: 'randomId' } },
|
||||
{ $sort: { _id: -1 } },
|
||||
{ $addFields: { _id: 1 } },
|
||||
{ $group: { _id: {} } },
|
||||
{ $project: { _id: 0 } },
|
||||
];
|
||||
const result = AggregateRouter.getPipeline(body);
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const analyticsAdapter = {
|
||||
appOpened: function () {},
|
||||
trackEvent: function () {},
|
||||
appOpened: function() {},
|
||||
trackEvent: function() {},
|
||||
};
|
||||
|
||||
describe('AnalyticsController', () => {
|
||||
|
||||
+160
-116
@@ -2,7 +2,8 @@ const auth = require('../lib/Auth');
|
||||
const Config = require('../lib/Config');
|
||||
const rest = require('../lib/rest');
|
||||
const request = require('../lib/request');
|
||||
const AudiencesRouter = require('../lib/Routers/AudiencesRouter').AudiencesRouter;
|
||||
const AudiencesRouter = require('../lib/Routers/AudiencesRouter')
|
||||
.AudiencesRouter;
|
||||
|
||||
describe('AudiencesRouter', () => {
|
||||
it('uses find condition from request.body', done => {
|
||||
@@ -31,7 +32,12 @@ describe('AudiencesRouter', () => {
|
||||
rest
|
||||
.create(config, auth.nobody(config), '_Audience', androidAudienceRequest)
|
||||
.then(() => {
|
||||
return rest.create(config, auth.nobody(config), '_Audience', iosAudienceRequest);
|
||||
return rest.create(
|
||||
config,
|
||||
auth.nobody(config),
|
||||
'_Audience',
|
||||
iosAudienceRequest
|
||||
);
|
||||
})
|
||||
.then(() => {
|
||||
return router.handleFind(request);
|
||||
@@ -73,7 +79,12 @@ describe('AudiencesRouter', () => {
|
||||
rest
|
||||
.create(config, auth.nobody(config), '_Audience', androidAudienceRequest)
|
||||
.then(() => {
|
||||
return rest.create(config, auth.nobody(config), '_Audience', iosAudienceRequest);
|
||||
return rest.create(
|
||||
config,
|
||||
auth.nobody(config),
|
||||
'_Audience',
|
||||
iosAudienceRequest
|
||||
);
|
||||
})
|
||||
.then(() => {
|
||||
return router.handleFind(request);
|
||||
@@ -114,7 +125,12 @@ describe('AudiencesRouter', () => {
|
||||
rest
|
||||
.create(config, auth.nobody(config), '_Audience', androidAudienceRequest)
|
||||
.then(() => {
|
||||
return rest.create(config, auth.nobody(config), '_Audience', iosAudienceRequest);
|
||||
return rest.create(
|
||||
config,
|
||||
auth.nobody(config),
|
||||
'_Audience',
|
||||
iosAudienceRequest
|
||||
);
|
||||
})
|
||||
.then(() => {
|
||||
return router.handleFind(request);
|
||||
@@ -153,7 +169,14 @@ describe('AudiencesRouter', () => {
|
||||
const router = new AudiencesRouter();
|
||||
rest
|
||||
.create(config, auth.nobody(config), '_Audience', androidAudienceRequest)
|
||||
.then(() => rest.create(config, auth.nobody(config), '_Audience', iosAudienceRequest))
|
||||
.then(() =>
|
||||
rest.create(
|
||||
config,
|
||||
auth.nobody(config),
|
||||
'_Audience',
|
||||
iosAudienceRequest
|
||||
)
|
||||
)
|
||||
.then(() => router.handleFind(request))
|
||||
.then(res => {
|
||||
const response = res.response;
|
||||
@@ -167,47 +190,60 @@ describe('AudiencesRouter', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it_exclude_dbs(['postgres'])('query installations with limit = 0 and count = 1', done => {
|
||||
const config = Config.get('test');
|
||||
const androidAudienceRequest = {
|
||||
name: 'Android Users',
|
||||
query: '{ "test": "android" }',
|
||||
};
|
||||
const iosAudienceRequest = {
|
||||
name: 'Iphone Users',
|
||||
query: '{ "test": "ios" }',
|
||||
};
|
||||
const request = {
|
||||
config: config,
|
||||
auth: auth.master(config),
|
||||
body: {},
|
||||
query: {
|
||||
limit: 0,
|
||||
count: 1,
|
||||
},
|
||||
info: {},
|
||||
};
|
||||
it_exclude_dbs(['postgres'])(
|
||||
'query installations with limit = 0 and count = 1',
|
||||
done => {
|
||||
const config = Config.get('test');
|
||||
const androidAudienceRequest = {
|
||||
name: 'Android Users',
|
||||
query: '{ "test": "android" }',
|
||||
};
|
||||
const iosAudienceRequest = {
|
||||
name: 'Iphone Users',
|
||||
query: '{ "test": "ios" }',
|
||||
};
|
||||
const request = {
|
||||
config: config,
|
||||
auth: auth.master(config),
|
||||
body: {},
|
||||
query: {
|
||||
limit: 0,
|
||||
count: 1,
|
||||
},
|
||||
info: {},
|
||||
};
|
||||
|
||||
const router = new AudiencesRouter();
|
||||
rest
|
||||
.create(config, auth.nobody(config), '_Audience', androidAudienceRequest)
|
||||
.then(() => {
|
||||
return rest.create(config, auth.nobody(config), '_Audience', iosAudienceRequest);
|
||||
})
|
||||
.then(() => {
|
||||
return router.handleFind(request);
|
||||
})
|
||||
.then(res => {
|
||||
const response = res.response;
|
||||
expect(response.results.length).toEqual(0);
|
||||
expect(response.count).toEqual(2);
|
||||
done();
|
||||
})
|
||||
.catch(err => {
|
||||
fail(JSON.stringify(err));
|
||||
done();
|
||||
});
|
||||
});
|
||||
const router = new AudiencesRouter();
|
||||
rest
|
||||
.create(
|
||||
config,
|
||||
auth.nobody(config),
|
||||
'_Audience',
|
||||
androidAudienceRequest
|
||||
)
|
||||
.then(() => {
|
||||
return rest.create(
|
||||
config,
|
||||
auth.nobody(config),
|
||||
'_Audience',
|
||||
iosAudienceRequest
|
||||
);
|
||||
})
|
||||
.then(() => {
|
||||
return router.handleFind(request);
|
||||
})
|
||||
.then(res => {
|
||||
const response = res.response;
|
||||
expect(response.results.length).toEqual(0);
|
||||
expect(response.count).toEqual(2);
|
||||
done();
|
||||
})
|
||||
.catch(err => {
|
||||
fail(JSON.stringify(err));
|
||||
done();
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
it('should create, read, update and delete audiences throw api', done => {
|
||||
Parse._request(
|
||||
@@ -216,49 +252,54 @@ describe('AudiencesRouter', () => {
|
||||
{ name: 'My Audience', query: JSON.stringify({ deviceType: 'ios' }) },
|
||||
{ useMasterKey: true }
|
||||
).then(() => {
|
||||
Parse._request('GET', 'push_audiences', {}, { useMasterKey: true }).then(results => {
|
||||
expect(results.results.length).toEqual(1);
|
||||
expect(results.results[0].name).toEqual('My Audience');
|
||||
expect(results.results[0].query.deviceType).toEqual('ios');
|
||||
Parse._request(
|
||||
'GET',
|
||||
`push_audiences/${results.results[0].objectId}`,
|
||||
{},
|
||||
{ useMasterKey: true }
|
||||
).then(results => {
|
||||
expect(results.name).toEqual('My Audience');
|
||||
expect(results.query.deviceType).toEqual('ios');
|
||||
Parse._request('GET', 'push_audiences', {}, { useMasterKey: true }).then(
|
||||
results => {
|
||||
expect(results.results.length).toEqual(1);
|
||||
expect(results.results[0].name).toEqual('My Audience');
|
||||
expect(results.results[0].query.deviceType).toEqual('ios');
|
||||
Parse._request(
|
||||
'PUT',
|
||||
`push_audiences/${results.objectId}`,
|
||||
{ name: 'My Audience 2' },
|
||||
'GET',
|
||||
`push_audiences/${results.results[0].objectId}`,
|
||||
{},
|
||||
{ useMasterKey: true }
|
||||
).then(() => {
|
||||
).then(results => {
|
||||
expect(results.name).toEqual('My Audience');
|
||||
expect(results.query.deviceType).toEqual('ios');
|
||||
Parse._request(
|
||||
'GET',
|
||||
'PUT',
|
||||
`push_audiences/${results.objectId}`,
|
||||
{},
|
||||
{ name: 'My Audience 2' },
|
||||
{ useMasterKey: true }
|
||||
).then(results => {
|
||||
expect(results.name).toEqual('My Audience 2');
|
||||
expect(results.query.deviceType).toEqual('ios');
|
||||
).then(() => {
|
||||
Parse._request(
|
||||
'DELETE',
|
||||
'GET',
|
||||
`push_audiences/${results.objectId}`,
|
||||
{},
|
||||
{ useMasterKey: true }
|
||||
).then(() => {
|
||||
Parse._request('GET', 'push_audiences', {}, { useMasterKey: true }).then(
|
||||
results => {
|
||||
).then(results => {
|
||||
expect(results.name).toEqual('My Audience 2');
|
||||
expect(results.query.deviceType).toEqual('ios');
|
||||
Parse._request(
|
||||
'DELETE',
|
||||
`push_audiences/${results.objectId}`,
|
||||
{},
|
||||
{ useMasterKey: true }
|
||||
).then(() => {
|
||||
Parse._request(
|
||||
'GET',
|
||||
'push_audiences',
|
||||
{},
|
||||
{ useMasterKey: true }
|
||||
).then(results => {
|
||||
expect(results.results.length).toEqual(0);
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -317,55 +358,58 @@ describe('AudiencesRouter', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it_exclude_dbs(['postgres'])('should support legacy parse.com audience fields', done => {
|
||||
const database = Config.get(Parse.applicationId).database.adapter.database;
|
||||
const now = new Date();
|
||||
Parse._request(
|
||||
'POST',
|
||||
'push_audiences',
|
||||
{ name: 'My Audience', query: JSON.stringify({ deviceType: 'ios' }) },
|
||||
{ useMasterKey: true }
|
||||
).then(audience => {
|
||||
database
|
||||
.collection('test__Audience')
|
||||
.updateOne(
|
||||
it_exclude_dbs(['postgres'])(
|
||||
'should support legacy parse.com audience fields',
|
||||
done => {
|
||||
const database = Config.get(Parse.applicationId).database.adapter
|
||||
.database;
|
||||
const now = new Date();
|
||||
Parse._request(
|
||||
'POST',
|
||||
'push_audiences',
|
||||
{ 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')
|
||||
.find({ _id: audience.objectId })
|
||||
.toArray((error, rows) => {
|
||||
expect(error).toEqual(null);
|
||||
expect(rows[0]['times_used']).toEqual(1);
|
||||
expect(rows[0]['_last_used']).toEqual(now);
|
||||
Parse._request(
|
||||
'GET',
|
||||
'push_audiences/' + audience.objectId,
|
||||
{},
|
||||
{ useMasterKey: true }
|
||||
)
|
||||
.then(audience => {
|
||||
expect(audience.name).toEqual('My Audience');
|
||||
expect(audience.query.deviceType).toEqual('ios');
|
||||
expect(audience.timesUsed).toEqual(1);
|
||||
expect(audience.lastUsed).toEqual(now.toISOString());
|
||||
done();
|
||||
})
|
||||
.catch(error => {
|
||||
done.fail(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
)
|
||||
.then(result => {
|
||||
expect(result).toBeTruthy();
|
||||
database
|
||||
.collection('test__Audience')
|
||||
.find({ _id: audience.objectId })
|
||||
.toArray((error, rows) => {
|
||||
expect(error).toEqual(undefined);
|
||||
expect(rows[0]['times_used']).toEqual(1);
|
||||
expect(rows[0]['_last_used']).toEqual(now);
|
||||
Parse._request(
|
||||
'GET',
|
||||
'push_audiences/' + audience.objectId,
|
||||
{},
|
||||
{ useMasterKey: true }
|
||||
)
|
||||
.then(audience => {
|
||||
expect(audience.name).toEqual('My Audience');
|
||||
expect(audience.query.deviceType).toEqual('ios');
|
||||
expect(audience.timesUsed).toEqual(1);
|
||||
expect(audience.lastUsed).toEqual(now.toISOString());
|
||||
done();
|
||||
})
|
||||
.catch(error => {
|
||||
done.fail(error);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
it('should be able to search on audiences', done => {
|
||||
Parse._request(
|
||||
|
||||
+11
-12
@@ -1,5 +1,3 @@
|
||||
'use strict';
|
||||
|
||||
describe('Auth', () => {
|
||||
const { Auth, getAuthForSessionToken } = require('../lib/Auth.js');
|
||||
const Config = require('../lib/Config');
|
||||
@@ -85,8 +83,8 @@ describe('Auth', () => {
|
||||
it('should properly handle bcrypt upgrade', done => {
|
||||
const bcryptOriginal = require('bcrypt-nodejs');
|
||||
const bcryptNew = require('bcryptjs');
|
||||
bcryptOriginal.hash('my1Long:password', null, null, function (err, res) {
|
||||
bcryptNew.compare('my1Long:password', res, function (err, res) {
|
||||
bcryptOriginal.hash('my1Long:password', null, null, function(err, res) {
|
||||
bcryptNew.compare('my1Long:password', res, function(err, res) {
|
||||
expect(res).toBeTruthy();
|
||||
done();
|
||||
});
|
||||
@@ -153,7 +151,7 @@ describe('Auth', () => {
|
||||
});
|
||||
|
||||
describe('getRolesForUser', () => {
|
||||
const rolesNumber = 100;
|
||||
const rolesNumber = 300;
|
||||
|
||||
it('should load all roles without config', async () => {
|
||||
const user = new Parse.User();
|
||||
@@ -170,9 +168,9 @@ describe('Auth', () => {
|
||||
const acl = new Parse.ACL();
|
||||
const role = new Parse.Role('roleloadtest' + i, acl);
|
||||
role.getUsers().add([user]);
|
||||
roles.push(role);
|
||||
roles.push(role.save());
|
||||
}
|
||||
const savedRoles = await Parse.Object.saveAll(roles);
|
||||
const savedRoles = await Promise.all(roles);
|
||||
expect(savedRoles.length).toBe(rolesNumber);
|
||||
const cloudRoles = await userAuth.getRolesForUser();
|
||||
expect(cloudRoles.length).toBe(rolesNumber);
|
||||
@@ -194,15 +192,16 @@ describe('Auth', () => {
|
||||
const acl = new Parse.ACL();
|
||||
const role = new Parse.Role('roleloadtest' + i, acl);
|
||||
role.getUsers().add([user]);
|
||||
roles.push(role);
|
||||
roles.push(role.save());
|
||||
}
|
||||
const savedRoles = await Parse.Object.saveAll(roles);
|
||||
const savedRoles = await Promise.all(roles);
|
||||
expect(savedRoles.length).toBe(rolesNumber);
|
||||
const cloudRoles = await userAuth.getRolesForUser();
|
||||
expect(cloudRoles.length).toBe(rolesNumber);
|
||||
});
|
||||
|
||||
it('should load all roles for different users with config', async () => {
|
||||
const rolesNumber = 100;
|
||||
const user = new Parse.User();
|
||||
await user.signUp({
|
||||
username: 'hello',
|
||||
@@ -230,10 +229,10 @@ describe('Auth', () => {
|
||||
const role2 = new Parse.Role('role2loadtest' + i, acl2);
|
||||
role.getUsers().add([user]);
|
||||
role2.getUsers().add([user2]);
|
||||
roles.push(role);
|
||||
roles.push(role2);
|
||||
roles.push(role.save());
|
||||
roles.push(role2.save());
|
||||
}
|
||||
const savedRoles = await Parse.Object.saveAll(roles);
|
||||
const savedRoles = await Promise.all(roles);
|
||||
expect(savedRoles.length).toBe(rolesNumber * 2);
|
||||
const cloudRoles = await userAuth.getRolesForUser();
|
||||
const cloudRoles2 = await user2Auth.getRolesForUser();
|
||||
|
||||
+245
-1356
File diff suppressed because it is too large
Load Diff
+45
-109
@@ -1,9 +1,8 @@
|
||||
'use strict';
|
||||
const commander = require('../lib/cli/utils/commander').default;
|
||||
const definitions = require('../lib/cli/definitions/parse-server').default;
|
||||
const liveQueryDefinitions = require('../lib/cli/definitions/parse-live-query-server').default;
|
||||
const path = require('path');
|
||||
const { spawn } = require('child_process');
|
||||
const liveQueryDefinitions = require('../lib/cli/definitions/parse-live-query-server')
|
||||
.default;
|
||||
|
||||
const testDefinitions = {
|
||||
arg0: 'PROGRAM_ARG_0',
|
||||
@@ -13,7 +12,7 @@ const testDefinitions = {
|
||||
},
|
||||
arg2: {
|
||||
env: 'PROGRAM_ARG_2',
|
||||
action: function (value) {
|
||||
action: function(value) {
|
||||
const intValue = parseInt(value);
|
||||
if (!Number.isInteger(intValue)) {
|
||||
throw 'arg2 is invalid';
|
||||
@@ -76,12 +75,15 @@ describe('commander additions', () => {
|
||||
|
||||
it('should load properly use args over env', done => {
|
||||
commander.loadDefinitions(testDefinitions);
|
||||
commander.parse(['node', './CLI.spec.js', '--arg0', 'arg0Value', '--arg4', ''], {
|
||||
PROGRAM_ARG_0: 'arg0ENVValue',
|
||||
PROGRAM_ARG_1: 'arg1ENVValue',
|
||||
PROGRAM_ARG_2: '4',
|
||||
PROGRAM_ARG_4: 'arg4ENVValue',
|
||||
});
|
||||
commander.parse(
|
||||
['node', './CLI.spec.js', '--arg0', 'arg0Value', '--arg4', ''],
|
||||
{
|
||||
PROGRAM_ARG_0: 'arg0ENVValue',
|
||||
PROGRAM_ARG_1: 'arg1ENVValue',
|
||||
PROGRAM_ARG_2: '4',
|
||||
PROGRAM_ARG_4: 'arg4ENVValue',
|
||||
}
|
||||
);
|
||||
expect(commander.arg0).toEqual('arg0Value');
|
||||
expect(commander.arg1).toEqual('arg1ENVValue');
|
||||
expect(commander.arg2).toEqual(4);
|
||||
@@ -102,10 +104,15 @@ describe('commander additions', () => {
|
||||
});
|
||||
|
||||
it('should not override config.json', done => {
|
||||
spyOn(console, 'log').and.callFake(() => {});
|
||||
commander.loadDefinitions(testDefinitions);
|
||||
commander.parse(
|
||||
['node', './CLI.spec.js', '--arg0', 'arg0Value', './spec/configs/CLIConfig.json'],
|
||||
[
|
||||
'node',
|
||||
'./CLI.spec.js',
|
||||
'--arg0',
|
||||
'arg0Value',
|
||||
'./spec/configs/CLIConfig.json',
|
||||
],
|
||||
{
|
||||
PROGRAM_ARG_0: 'arg0ENVValue',
|
||||
PROGRAM_ARG_1: 'arg1ENVValue',
|
||||
@@ -122,7 +129,13 @@ describe('commander additions', () => {
|
||||
commander.loadDefinitions(testDefinitions);
|
||||
expect(() => {
|
||||
commander.parse(
|
||||
['node', './CLI.spec.js', '--arg0', 'arg0Value', './spec/configs/CLIConfigFail.json'],
|
||||
[
|
||||
'node',
|
||||
'./CLI.spec.js',
|
||||
'--arg0',
|
||||
'arg0Value',
|
||||
'./spec/configs/CLIConfigFail.json',
|
||||
],
|
||||
{
|
||||
PROGRAM_ARG_0: 'arg0ENVValue',
|
||||
PROGRAM_ARG_1: 'arg1ENVValue',
|
||||
@@ -135,15 +148,22 @@ describe('commander additions', () => {
|
||||
it('should fail when too many apps are set', done => {
|
||||
commander.loadDefinitions(testDefinitions);
|
||||
expect(() => {
|
||||
commander.parse(['node', './CLI.spec.js', './spec/configs/CLIConfigFailTooManyApps.json']);
|
||||
commander.parse([
|
||||
'node',
|
||||
'./CLI.spec.js',
|
||||
'./spec/configs/CLIConfigFailTooManyApps.json',
|
||||
]);
|
||||
}).toThrow('Multiple apps are not supported');
|
||||
done();
|
||||
});
|
||||
|
||||
it('should load config from apps', done => {
|
||||
spyOn(console, 'log').and.callFake(() => {});
|
||||
commander.loadDefinitions(testDefinitions);
|
||||
commander.parse(['node', './CLI.spec.js', './spec/configs/CLIConfigApps.json']);
|
||||
commander.parse([
|
||||
'node',
|
||||
'./CLI.spec.js',
|
||||
'./spec/configs/CLIConfigApps.json',
|
||||
]);
|
||||
const options = commander.getOptions();
|
||||
expect(options.arg1).toBe('my_app');
|
||||
expect(options.arg2).toBe(8888);
|
||||
@@ -155,7 +175,11 @@ describe('commander additions', () => {
|
||||
it('should fail when passing an invalid arguement', done => {
|
||||
commander.loadDefinitions(testDefinitions);
|
||||
expect(() => {
|
||||
commander.parse(['node', './CLI.spec.js', './spec/configs/CLIConfigUnknownArg.json']);
|
||||
commander.parse([
|
||||
'node',
|
||||
'./CLI.spec.js',
|
||||
'./spec/configs/CLIConfigUnknownArg.json',
|
||||
]);
|
||||
}).toThrow('error: unknown option myArg');
|
||||
done();
|
||||
});
|
||||
@@ -194,7 +218,10 @@ describe('LiveQuery definitions', () => {
|
||||
if (typeof definition.env !== 'undefined') {
|
||||
expect(typeof definition.env).toBe('string');
|
||||
}
|
||||
expect(typeof definition.help).toBe('string', `help for ${key} should be a string`);
|
||||
expect(typeof definition.help).toBe(
|
||||
'string',
|
||||
`help for ${key} should be a string`
|
||||
);
|
||||
if (typeof definition.required !== 'undefined') {
|
||||
expect(typeof definition.required).toBe('boolean');
|
||||
}
|
||||
@@ -204,94 +231,3 @@ describe('LiveQuery definitions', () => {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('execution', () => {
|
||||
const binPath = path.resolve(__dirname, '../bin/parse-server');
|
||||
let childProcess;
|
||||
|
||||
afterEach(done => {
|
||||
if (childProcess) {
|
||||
childProcess.on('close', () => {
|
||||
childProcess = undefined;
|
||||
done();
|
||||
});
|
||||
childProcess.kill();
|
||||
}
|
||||
});
|
||||
|
||||
it('shoud start Parse Server', done => {
|
||||
childProcess = spawn(binPath, [
|
||||
'--appId',
|
||||
'test',
|
||||
'--masterKey',
|
||||
'test',
|
||||
'--databaseURI',
|
||||
'mongodb://localhost/test',
|
||||
'--port',
|
||||
'1339',
|
||||
]);
|
||||
childProcess.stdout.on('data', data => {
|
||||
data = data.toString();
|
||||
if (data.includes('parse-server running on')) {
|
||||
done();
|
||||
}
|
||||
});
|
||||
childProcess.stderr.on('data', data => {
|
||||
done.fail(data.toString());
|
||||
});
|
||||
});
|
||||
|
||||
it('shoud start Parse Server with GraphQL', done => {
|
||||
childProcess = spawn(binPath, [
|
||||
'--appId',
|
||||
'test',
|
||||
'--masterKey',
|
||||
'test',
|
||||
'--databaseURI',
|
||||
'mongodb://localhost/test',
|
||||
'--port',
|
||||
'1340',
|
||||
'--mountGraphQL',
|
||||
]);
|
||||
let output = '';
|
||||
childProcess.stdout.on('data', data => {
|
||||
data = data.toString();
|
||||
output += data;
|
||||
if (data.includes('GraphQL running on')) {
|
||||
expect(output).toMatch('parse-server running on');
|
||||
done();
|
||||
}
|
||||
});
|
||||
childProcess.stderr.on('data', data => {
|
||||
done.fail(data.toString());
|
||||
});
|
||||
});
|
||||
|
||||
it('shoud start Parse Server with GraphQL and Playground', done => {
|
||||
childProcess = spawn(binPath, [
|
||||
'--appId',
|
||||
'test',
|
||||
'--masterKey',
|
||||
'test',
|
||||
'--databaseURI',
|
||||
'mongodb://localhost/test',
|
||||
'--port',
|
||||
'1341',
|
||||
'--mountGraphQL',
|
||||
'--mountPlayground',
|
||||
]);
|
||||
let output = '';
|
||||
childProcess.stdout.on('data', data => {
|
||||
data = data.toString();
|
||||
output += data;
|
||||
if (data.includes('Playground running on')) {
|
||||
expect(output).toMatch('GraphQL running on');
|
||||
expect(output).toMatch('parse-server running on');
|
||||
done();
|
||||
}
|
||||
});
|
||||
childProcess.stderr.on('data', data => {
|
||||
done.fail(data.toString());
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const CacheController = require('../lib/Controllers/CacheController.js').default;
|
||||
const CacheController = require('../lib/Controllers/CacheController.js')
|
||||
.default;
|
||||
|
||||
describe('CacheController', function () {
|
||||
describe('CacheController', function() {
|
||||
let FakeCacheAdapter;
|
||||
const FakeAppID = 'foo';
|
||||
const KEY = 'hello';
|
||||
|
||||
+29
-22
@@ -1,8 +1,9 @@
|
||||
const Client = require('../lib/LiveQuery/Client').Client;
|
||||
const ParseWebSocket = require('../lib/LiveQuery/ParseWebSocketServer').ParseWebSocket;
|
||||
const ParseWebSocket = require('../lib/LiveQuery/ParseWebSocketServer')
|
||||
.ParseWebSocket;
|
||||
|
||||
describe('Client', function () {
|
||||
it('can be initialized', function () {
|
||||
describe('Client', function() {
|
||||
it('can be initialized', function() {
|
||||
const parseWebSocket = new ParseWebSocket({});
|
||||
const client = new Client(1, parseWebSocket);
|
||||
|
||||
@@ -11,7 +12,7 @@ describe('Client', function () {
|
||||
expect(client.subscriptionInfos.size).toBe(0);
|
||||
});
|
||||
|
||||
it('can push response', function () {
|
||||
it('can push response', function() {
|
||||
const parseWebSocket = {
|
||||
send: jasmine.createSpy('send'),
|
||||
};
|
||||
@@ -20,7 +21,7 @@ describe('Client', function () {
|
||||
expect(parseWebSocket.send).toHaveBeenCalledWith('message');
|
||||
});
|
||||
|
||||
it('can push error', function () {
|
||||
it('can push error', function() {
|
||||
const parseWebSocket = {
|
||||
send: jasmine.createSpy('send'),
|
||||
};
|
||||
@@ -34,7 +35,7 @@ describe('Client', function () {
|
||||
expect(messageJSON.reconnect).toBe(true);
|
||||
});
|
||||
|
||||
it('can add subscription information', function () {
|
||||
it('can add subscription information', function() {
|
||||
const subscription = {};
|
||||
const fields = ['test'];
|
||||
const subscriptionInfo = {
|
||||
@@ -48,7 +49,7 @@ describe('Client', function () {
|
||||
expect(client.subscriptionInfos.get(1)).toBe(subscriptionInfo);
|
||||
});
|
||||
|
||||
it('can get subscription information', function () {
|
||||
it('can get subscription information', function() {
|
||||
const subscription = {};
|
||||
const fields = ['test'];
|
||||
const subscriptionInfo = {
|
||||
@@ -62,7 +63,7 @@ describe('Client', function () {
|
||||
expect(subscriptionInfoAgain).toBe(subscriptionInfo);
|
||||
});
|
||||
|
||||
it('can delete subscription information', function () {
|
||||
it('can delete subscription information', function() {
|
||||
const subscription = {};
|
||||
const fields = ['test'];
|
||||
const subscriptionInfo = {
|
||||
@@ -76,7 +77,7 @@ describe('Client', function () {
|
||||
expect(client.subscriptionInfos.size).toBe(0);
|
||||
});
|
||||
|
||||
it('can generate ParseObject JSON with null selected field', function () {
|
||||
it('can generate ParseObject JSON with null selected field', function() {
|
||||
const parseObjectJSON = {
|
||||
key: 'value',
|
||||
className: 'test',
|
||||
@@ -87,10 +88,12 @@ describe('Client', function () {
|
||||
};
|
||||
const client = new Client(1, {});
|
||||
|
||||
expect(client._toJSONWithFields(parseObjectJSON, null)).toBe(parseObjectJSON);
|
||||
expect(client._toJSONWithFields(parseObjectJSON, null)).toBe(
|
||||
parseObjectJSON
|
||||
);
|
||||
});
|
||||
|
||||
it('can generate ParseObject JSON with undefined selected field', function () {
|
||||
it('can generate ParseObject JSON with undefined selected field', function() {
|
||||
const parseObjectJSON = {
|
||||
key: 'value',
|
||||
className: 'test',
|
||||
@@ -101,10 +104,12 @@ describe('Client', function () {
|
||||
};
|
||||
const client = new Client(1, {});
|
||||
|
||||
expect(client._toJSONWithFields(parseObjectJSON, undefined)).toBe(parseObjectJSON);
|
||||
expect(client._toJSONWithFields(parseObjectJSON, undefined)).toBe(
|
||||
parseObjectJSON
|
||||
);
|
||||
});
|
||||
|
||||
it('can generate ParseObject JSON with selected fields', function () {
|
||||
it('can generate ParseObject JSON with selected fields', function() {
|
||||
const parseObjectJSON = {
|
||||
key: 'value',
|
||||
className: 'test',
|
||||
@@ -126,7 +131,7 @@ describe('Client', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('can generate ParseObject JSON with nonexistent selected fields', function () {
|
||||
it('can generate ParseObject JSON with nonexistent selected fields', function() {
|
||||
const parseObjectJSON = {
|
||||
key: 'value',
|
||||
className: 'test',
|
||||
@@ -137,7 +142,9 @@ describe('Client', function () {
|
||||
test: 'test',
|
||||
};
|
||||
const client = new Client(1, {});
|
||||
const limitedParseObject = client._toJSONWithFields(parseObjectJSON, ['name']);
|
||||
const limitedParseObject = client._toJSONWithFields(parseObjectJSON, [
|
||||
'name',
|
||||
]);
|
||||
|
||||
expect(limitedParseObject).toEqual({
|
||||
className: 'test',
|
||||
@@ -149,7 +156,7 @@ describe('Client', function () {
|
||||
expect('name' in limitedParseObject).toBe(false);
|
||||
});
|
||||
|
||||
it('can push connect response', function () {
|
||||
it('can push connect response', function() {
|
||||
const parseWebSocket = {
|
||||
send: jasmine.createSpy('send'),
|
||||
};
|
||||
@@ -162,7 +169,7 @@ describe('Client', function () {
|
||||
expect(messageJSON.clientId).toBe(1);
|
||||
});
|
||||
|
||||
it('can push subscribe response', function () {
|
||||
it('can push subscribe response', function() {
|
||||
const parseWebSocket = {
|
||||
send: jasmine.createSpy('send'),
|
||||
};
|
||||
@@ -176,7 +183,7 @@ describe('Client', function () {
|
||||
expect(messageJSON.requestId).toBe(2);
|
||||
});
|
||||
|
||||
it('can push unsubscribe response', function () {
|
||||
it('can push unsubscribe response', function() {
|
||||
const parseWebSocket = {
|
||||
send: jasmine.createSpy('send'),
|
||||
};
|
||||
@@ -190,7 +197,7 @@ describe('Client', function () {
|
||||
expect(messageJSON.requestId).toBe(2);
|
||||
});
|
||||
|
||||
it('can push create response', function () {
|
||||
it('can push create response', function() {
|
||||
const parseObjectJSON = {
|
||||
key: 'value',
|
||||
className: 'test',
|
||||
@@ -214,7 +221,7 @@ describe('Client', function () {
|
||||
expect(messageJSON.object).toEqual(parseObjectJSON);
|
||||
});
|
||||
|
||||
it('can push enter response', function () {
|
||||
it('can push enter response', function() {
|
||||
const parseObjectJSON = {
|
||||
key: 'value',
|
||||
className: 'test',
|
||||
@@ -238,7 +245,7 @@ describe('Client', function () {
|
||||
expect(messageJSON.object).toEqual(parseObjectJSON);
|
||||
});
|
||||
|
||||
it('can push update response', function () {
|
||||
it('can push update response', function() {
|
||||
const parseObjectJSON = {
|
||||
key: 'value',
|
||||
className: 'test',
|
||||
@@ -262,7 +269,7 @@ describe('Client', function () {
|
||||
expect(messageJSON.object).toEqual(parseObjectJSON);
|
||||
});
|
||||
|
||||
it('can push leave response', function () {
|
||||
it('can push leave response', function() {
|
||||
const parseObjectJSON = {
|
||||
key: 'value',
|
||||
className: 'test',
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+292
-1549
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,5 @@
|
||||
const LoggerController = require('../lib/Controllers/LoggerController').LoggerController;
|
||||
const LoggerController = require('../lib/Controllers/LoggerController')
|
||||
.LoggerController;
|
||||
const WinstonLoggerAdapter = require('../lib/Adapters/Logger/WinstonLoggerAdapter')
|
||||
.WinstonLoggerAdapter;
|
||||
const fs = require('fs');
|
||||
@@ -14,7 +15,6 @@ describe('Cloud Code Logger', () => {
|
||||
return reconfigureServer({
|
||||
// useful to flip to false for fine tuning :).
|
||||
silent: true,
|
||||
logLevel: undefined,
|
||||
})
|
||||
.then(() => {
|
||||
return Parse.User.signUp('tester', 'abc')
|
||||
@@ -23,7 +23,10 @@ describe('Cloud Code Logger', () => {
|
||||
.then(() => Parse.User.logIn(user.get('username'), 'abc'));
|
||||
})
|
||||
.then(() => {
|
||||
spy = spyOn(Config.get('test').loggerController.adapter, 'log').and.callThrough();
|
||||
spy = spyOn(
|
||||
Config.get('test').loggerController.adapter,
|
||||
'log'
|
||||
).and.callThrough();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -31,7 +34,10 @@ describe('Cloud Code Logger', () => {
|
||||
// see helpers.js:afterEach
|
||||
|
||||
it('should expose log to functions', () => {
|
||||
const spy = spyOn(Config.get('test').loggerController, 'log').and.callThrough();
|
||||
const spy = spyOn(
|
||||
Config.get('test').loggerController,
|
||||
'log'
|
||||
).and.callThrough();
|
||||
Parse.Cloud.define('loggerTest', req => {
|
||||
req.log.info('logTest', 'info log', { info: 'some log' });
|
||||
req.log.error('logTest', 'error log', { error: 'there was an error' });
|
||||
@@ -48,7 +54,9 @@ describe('Cloud Code Logger', () => {
|
||||
expect(cloudFunctionMessage.args[1][0]).toMatch(
|
||||
/Ran cloud function loggerTest for user [^ ]* with:\n {2}Input: {}\n {2}Result: {}/
|
||||
);
|
||||
expect(cloudFunctionMessage.args[1][1].functionName).toEqual('loggerTest');
|
||||
expect(cloudFunctionMessage.args[1][1].functionName).toEqual(
|
||||
'loggerTest'
|
||||
);
|
||||
expect(errorMessage.args[0]).toBe('error');
|
||||
expect(errorMessage.args[1][2].error).toBe('there was an error');
|
||||
expect(errorMessage.args[1][0]).toBe('logTest');
|
||||
@@ -98,10 +106,14 @@ describe('Cloud Code Logger', () => {
|
||||
expect(cloudTriggerMessage[2].user).toBe(user.id);
|
||||
expect(errorMessage[0]).toBe('error');
|
||||
expect(errorMessage[3].error).toBe('there was an error');
|
||||
expect(errorMessage[1] + ' ' + errorMessage[2]).toBe('beforeSave MyObject error log');
|
||||
expect(errorMessage[1] + ' ' + errorMessage[2]).toBe(
|
||||
'beforeSave MyObject error log'
|
||||
);
|
||||
expect(infoMessage[0]).toBe('info');
|
||||
expect(infoMessage[3].info).toBe('some log');
|
||||
expect(infoMessage[1] + ' ' + infoMessage[2]).toBe('beforeSave MyObject info log');
|
||||
expect(infoMessage[1] + ' ' + infoMessage[2]).toBe(
|
||||
'beforeSave MyObject info log'
|
||||
);
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -161,7 +173,7 @@ describe('Cloud Code Logger', () => {
|
||||
expect(log[0]).toEqual('error');
|
||||
const error = log[2].error;
|
||||
expect(error instanceof Parse.Error).toBeTruthy();
|
||||
expect(error.code).toBe(Parse.Error.SCRIPT_FAILED);
|
||||
expect(error.code).toBe(141);
|
||||
expect(error.message).toBe('uh oh!');
|
||||
done();
|
||||
});
|
||||
@@ -199,9 +211,7 @@ describe('Cloud Code Logger', () => {
|
||||
expect(log[1]).toMatch(
|
||||
/Failed running cloud function aFunction for user [^ ]* with:\n {2}Input: {"foo":"bar"}\n {2}Error:/
|
||||
);
|
||||
const errorString = JSON.stringify(
|
||||
new Parse.Error(Parse.Error.SCRIPT_FAILED, 'it failed!')
|
||||
);
|
||||
const errorString = JSON.stringify(new Parse.Error(141, 'it failed!'));
|
||||
expect(log[1].indexOf(errorString)).toBeGreaterThan(0);
|
||||
done();
|
||||
})
|
||||
|
||||
+14
-343
@@ -1,9 +1,9 @@
|
||||
const DatabaseController = require('../lib/Controllers/DatabaseController.js');
|
||||
const validateQuery = DatabaseController._validateQuery;
|
||||
|
||||
describe('DatabaseController', function () {
|
||||
describe('validateQuery', function () {
|
||||
it('should not restructure simple cases of SERVER-13732', done => {
|
||||
describe('DatabaseController', function() {
|
||||
describe('validateQuery', function() {
|
||||
it('should restructure simple cases of SERVER-13732', done => {
|
||||
const query = {
|
||||
$or: [{ a: 1 }, { a: 2 }],
|
||||
_rperm: { $in: ['a', 'b'] },
|
||||
@@ -11,9 +11,10 @@ describe('DatabaseController', function () {
|
||||
};
|
||||
validateQuery(query);
|
||||
expect(query).toEqual({
|
||||
$or: [{ a: 1 }, { a: 2 }],
|
||||
_rperm: { $in: ['a', 'b'] },
|
||||
foo: 3,
|
||||
$or: [
|
||||
{ a: 1, _rperm: { $in: ['a', 'b'] }, foo: 3 },
|
||||
{ a: 2, _rperm: { $in: ['a', 'b'] }, foo: 3 },
|
||||
],
|
||||
});
|
||||
done();
|
||||
});
|
||||
@@ -25,21 +26,25 @@ describe('DatabaseController', function () {
|
||||
$or: [{ a: 1 }, { b: 1 }],
|
||||
c: { $nearSphere: {} },
|
||||
});
|
||||
|
||||
query = { $or: [{ a: 1 }, { b: 1 }], c: { $near: {} } };
|
||||
validateQuery(query);
|
||||
expect(query).toEqual({ $or: [{ a: 1 }, { b: 1 }], c: { $near: {} } });
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
it('should not push refactored keys down a tree for SERVER-13732', done => {
|
||||
it('should push refactored keys down a tree for SERVER-13732', done => {
|
||||
const query = {
|
||||
a: 1,
|
||||
$or: [{ $or: [{ b: 1 }, { b: 2 }] }, { $or: [{ c: 1 }, { c: 2 }] }],
|
||||
};
|
||||
validateQuery(query);
|
||||
expect(query).toEqual({
|
||||
a: 1,
|
||||
$or: [{ $or: [{ b: 1 }, { b: 2 }] }, { $or: [{ c: 1 }, { c: 2 }] }],
|
||||
$or: [
|
||||
{ $or: [{ b: 1, a: 1 }, { b: 2, a: 1 }] },
|
||||
{ $or: [{ c: 1, a: 1 }, { c: 2, a: 1 }] },
|
||||
],
|
||||
});
|
||||
|
||||
done();
|
||||
@@ -55,338 +60,4 @@ describe('DatabaseController', function () {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('addPointerPermissions', function () {
|
||||
const CLASS_NAME = 'Foo';
|
||||
const USER_ID = 'userId';
|
||||
const ACL_GROUP = [USER_ID];
|
||||
const OPERATION = 'find';
|
||||
|
||||
const databaseController = new DatabaseController();
|
||||
const schemaController = jasmine.createSpyObj('SchemaController', [
|
||||
'testPermissionsForClassName',
|
||||
'getClassLevelPermissions',
|
||||
'getExpectedType',
|
||||
]);
|
||||
|
||||
it('should not decorate query if no pointer CLPs are present', done => {
|
||||
const clp = buildCLP();
|
||||
const query = { a: 'b' };
|
||||
|
||||
schemaController.testPermissionsForClassName
|
||||
.withArgs(CLASS_NAME, ACL_GROUP, OPERATION)
|
||||
.and.returnValue(true);
|
||||
schemaController.getClassLevelPermissions.withArgs(CLASS_NAME).and.returnValue(clp);
|
||||
|
||||
const output = databaseController.addPointerPermissions(
|
||||
schemaController,
|
||||
CLASS_NAME,
|
||||
OPERATION,
|
||||
query,
|
||||
ACL_GROUP
|
||||
);
|
||||
|
||||
expect(output).toEqual({ ...query });
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
it('should decorate query if a pointer CLP entry is present', done => {
|
||||
const clp = buildCLP(['user']);
|
||||
const query = { a: 'b' };
|
||||
|
||||
schemaController.testPermissionsForClassName
|
||||
.withArgs(CLASS_NAME, ACL_GROUP, OPERATION)
|
||||
.and.returnValue(false);
|
||||
schemaController.getClassLevelPermissions.withArgs(CLASS_NAME).and.returnValue(clp);
|
||||
schemaController.getExpectedType
|
||||
.withArgs(CLASS_NAME, 'user')
|
||||
.and.returnValue({ type: 'Pointer' });
|
||||
|
||||
const output = databaseController.addPointerPermissions(
|
||||
schemaController,
|
||||
CLASS_NAME,
|
||||
OPERATION,
|
||||
query,
|
||||
ACL_GROUP
|
||||
);
|
||||
|
||||
expect(output).toEqual({ ...query, user: createUserPointer(USER_ID) });
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
it('should decorate query if an array CLP entry is present', done => {
|
||||
const clp = buildCLP(['users']);
|
||||
const query = { a: 'b' };
|
||||
|
||||
schemaController.testPermissionsForClassName
|
||||
.withArgs(CLASS_NAME, ACL_GROUP, OPERATION)
|
||||
.and.returnValue(false);
|
||||
schemaController.getClassLevelPermissions.withArgs(CLASS_NAME).and.returnValue(clp);
|
||||
schemaController.getExpectedType
|
||||
.withArgs(CLASS_NAME, 'users')
|
||||
.and.returnValue({ type: 'Array' });
|
||||
|
||||
const output = databaseController.addPointerPermissions(
|
||||
schemaController,
|
||||
CLASS_NAME,
|
||||
OPERATION,
|
||||
query,
|
||||
ACL_GROUP
|
||||
);
|
||||
|
||||
expect(output).toEqual({
|
||||
...query,
|
||||
users: { $all: [createUserPointer(USER_ID)] },
|
||||
});
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
it('should decorate query if an object CLP entry is present', done => {
|
||||
const clp = buildCLP(['user']);
|
||||
const query = { a: 'b' };
|
||||
|
||||
schemaController.testPermissionsForClassName
|
||||
.withArgs(CLASS_NAME, ACL_GROUP, OPERATION)
|
||||
.and.returnValue(false);
|
||||
schemaController.getClassLevelPermissions.withArgs(CLASS_NAME).and.returnValue(clp);
|
||||
schemaController.getExpectedType
|
||||
.withArgs(CLASS_NAME, 'user')
|
||||
.and.returnValue({ type: 'Object' });
|
||||
|
||||
const output = databaseController.addPointerPermissions(
|
||||
schemaController,
|
||||
CLASS_NAME,
|
||||
OPERATION,
|
||||
query,
|
||||
ACL_GROUP
|
||||
);
|
||||
|
||||
expect(output).toEqual({
|
||||
...query,
|
||||
user: createUserPointer(USER_ID),
|
||||
});
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
it('should decorate query if a pointer CLP is present and the same field is part of the query', done => {
|
||||
const clp = buildCLP(['user']);
|
||||
const query = { a: 'b', user: 'a' };
|
||||
|
||||
schemaController.testPermissionsForClassName
|
||||
.withArgs(CLASS_NAME, ACL_GROUP, OPERATION)
|
||||
.and.returnValue(false);
|
||||
schemaController.getClassLevelPermissions.withArgs(CLASS_NAME).and.returnValue(clp);
|
||||
schemaController.getExpectedType
|
||||
.withArgs(CLASS_NAME, 'user')
|
||||
.and.returnValue({ type: 'Pointer' });
|
||||
|
||||
const output = databaseController.addPointerPermissions(
|
||||
schemaController,
|
||||
CLASS_NAME,
|
||||
OPERATION,
|
||||
query,
|
||||
ACL_GROUP
|
||||
);
|
||||
|
||||
expect(output).toEqual({
|
||||
$and: [{ user: createUserPointer(USER_ID) }, { ...query }],
|
||||
});
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
it('should transform the query to an $or query if multiple array/pointer CLPs are present', done => {
|
||||
const clp = buildCLP(['user', 'users', 'userObject']);
|
||||
const query = { a: 'b' };
|
||||
|
||||
schemaController.testPermissionsForClassName
|
||||
.withArgs(CLASS_NAME, ACL_GROUP, OPERATION)
|
||||
.and.returnValue(false);
|
||||
schemaController.getClassLevelPermissions.withArgs(CLASS_NAME).and.returnValue(clp);
|
||||
schemaController.getExpectedType
|
||||
.withArgs(CLASS_NAME, 'user')
|
||||
.and.returnValue({ type: 'Pointer' });
|
||||
schemaController.getExpectedType
|
||||
.withArgs(CLASS_NAME, 'users')
|
||||
.and.returnValue({ type: 'Array' });
|
||||
schemaController.getExpectedType
|
||||
.withArgs(CLASS_NAME, 'userObject')
|
||||
.and.returnValue({ type: 'Object' });
|
||||
|
||||
const output = databaseController.addPointerPermissions(
|
||||
schemaController,
|
||||
CLASS_NAME,
|
||||
OPERATION,
|
||||
query,
|
||||
ACL_GROUP
|
||||
);
|
||||
|
||||
expect(output).toEqual({
|
||||
$or: [
|
||||
{ ...query, user: createUserPointer(USER_ID) },
|
||||
{ ...query, users: { $all: [createUserPointer(USER_ID)] } },
|
||||
{ ...query, userObject: createUserPointer(USER_ID) },
|
||||
],
|
||||
});
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
it('should not return a $or operation if the query involves one of the two fields also used as array/pointer permissions', done => {
|
||||
const clp = buildCLP(['users', 'user']);
|
||||
const query = { a: 'b', user: createUserPointer(USER_ID) };
|
||||
schemaController.testPermissionsForClassName
|
||||
.withArgs(CLASS_NAME, ACL_GROUP, OPERATION)
|
||||
.and.returnValue(false);
|
||||
schemaController.getClassLevelPermissions.withArgs(CLASS_NAME).and.returnValue(clp);
|
||||
schemaController.getExpectedType
|
||||
.withArgs(CLASS_NAME, 'user')
|
||||
.and.returnValue({ type: 'Pointer' });
|
||||
schemaController.getExpectedType
|
||||
.withArgs(CLASS_NAME, 'users')
|
||||
.and.returnValue({ type: 'Array' });
|
||||
const output = databaseController.addPointerPermissions(
|
||||
schemaController,
|
||||
CLASS_NAME,
|
||||
OPERATION,
|
||||
query,
|
||||
ACL_GROUP
|
||||
);
|
||||
expect(output).toEqual({ ...query, user: createUserPointer(USER_ID) });
|
||||
done();
|
||||
});
|
||||
|
||||
it('should not return a $or operation if the query involves one of the fields also used as array/pointer permissions', done => {
|
||||
const clp = buildCLP(['user', 'users', 'userObject']);
|
||||
const query = { a: 'b', user: createUserPointer(USER_ID) };
|
||||
schemaController.testPermissionsForClassName
|
||||
.withArgs(CLASS_NAME, ACL_GROUP, OPERATION)
|
||||
.and.returnValue(false);
|
||||
schemaController.getClassLevelPermissions.withArgs(CLASS_NAME).and.returnValue(clp);
|
||||
schemaController.getExpectedType
|
||||
.withArgs(CLASS_NAME, 'user')
|
||||
.and.returnValue({ type: 'Pointer' });
|
||||
schemaController.getExpectedType
|
||||
.withArgs(CLASS_NAME, 'users')
|
||||
.and.returnValue({ type: 'Array' });
|
||||
schemaController.getExpectedType
|
||||
.withArgs(CLASS_NAME, 'userObject')
|
||||
.and.returnValue({ type: 'Object' });
|
||||
const output = databaseController.addPointerPermissions(
|
||||
schemaController,
|
||||
CLASS_NAME,
|
||||
OPERATION,
|
||||
query,
|
||||
ACL_GROUP
|
||||
);
|
||||
expect(output).toEqual({ ...query, user: createUserPointer(USER_ID) });
|
||||
done();
|
||||
});
|
||||
|
||||
it('should throw an error if for some unexpected reason the property specified in the CLP is neither a pointer nor an array', done => {
|
||||
const clp = buildCLP(['user']);
|
||||
const query = { a: 'b' };
|
||||
|
||||
schemaController.testPermissionsForClassName
|
||||
.withArgs(CLASS_NAME, ACL_GROUP, OPERATION)
|
||||
.and.returnValue(false);
|
||||
schemaController.getClassLevelPermissions.withArgs(CLASS_NAME).and.returnValue(clp);
|
||||
schemaController.getExpectedType
|
||||
.withArgs(CLASS_NAME, 'user')
|
||||
.and.returnValue({ type: 'Number' });
|
||||
|
||||
expect(() => {
|
||||
databaseController.addPointerPermissions(
|
||||
schemaController,
|
||||
CLASS_NAME,
|
||||
OPERATION,
|
||||
query,
|
||||
ACL_GROUP
|
||||
);
|
||||
}).toThrow(
|
||||
Error(
|
||||
`An unexpected condition occurred when resolving pointer permissions: ${CLASS_NAME} user`
|
||||
)
|
||||
);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('reduceOperations', function () {
|
||||
const databaseController = new DatabaseController();
|
||||
|
||||
it('objectToEntriesStrings', done => {
|
||||
const output = databaseController.objectToEntriesStrings({ a: 1, b: 2, c: 3 });
|
||||
expect(output).toEqual(['"a":1', '"b":2', '"c":3']);
|
||||
done();
|
||||
});
|
||||
|
||||
it('reduceOrOperation', done => {
|
||||
expect(databaseController.reduceOrOperation({ a: 1 })).toEqual({ a: 1 });
|
||||
expect(databaseController.reduceOrOperation({ $or: [{ a: 1 }, { b: 2 }] })).toEqual({
|
||||
$or: [{ a: 1 }, { b: 2 }],
|
||||
});
|
||||
expect(databaseController.reduceOrOperation({ $or: [{ a: 1 }, { a: 2 }] })).toEqual({
|
||||
$or: [{ a: 1 }, { a: 2 }],
|
||||
});
|
||||
expect(databaseController.reduceOrOperation({ $or: [{ a: 1 }, { a: 1 }] })).toEqual({ a: 1 });
|
||||
expect(
|
||||
databaseController.reduceOrOperation({ $or: [{ a: 1, b: 2, c: 3 }, { a: 1 }] })
|
||||
).toEqual({ a: 1 });
|
||||
expect(
|
||||
databaseController.reduceOrOperation({ $or: [{ b: 2 }, { a: 1, b: 2, c: 3 }] })
|
||||
).toEqual({ b: 2 });
|
||||
done();
|
||||
});
|
||||
|
||||
it('reduceAndOperation', done => {
|
||||
expect(databaseController.reduceAndOperation({ a: 1 })).toEqual({ a: 1 });
|
||||
expect(databaseController.reduceAndOperation({ $and: [{ a: 1 }, { b: 2 }] })).toEqual({
|
||||
$and: [{ a: 1 }, { b: 2 }],
|
||||
});
|
||||
expect(databaseController.reduceAndOperation({ $and: [{ a: 1 }, { a: 2 }] })).toEqual({
|
||||
$and: [{ a: 1 }, { a: 2 }],
|
||||
});
|
||||
expect(databaseController.reduceAndOperation({ $and: [{ a: 1 }, { a: 1 }] })).toEqual({
|
||||
a: 1,
|
||||
});
|
||||
expect(
|
||||
databaseController.reduceAndOperation({ $and: [{ a: 1, b: 2, c: 3 }, { b: 2 }] })
|
||||
).toEqual({ a: 1, b: 2, c: 3 });
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function buildCLP(pointerNames) {
|
||||
const OPERATIONS = ['count', 'find', 'get', 'create', 'update', 'delete', 'addField'];
|
||||
|
||||
const clp = OPERATIONS.reduce((acc, op) => {
|
||||
acc[op] = {};
|
||||
|
||||
if (pointerNames && pointerNames.length) {
|
||||
acc[op].pointerFields = pointerNames;
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
clp.protectedFields = {};
|
||||
clp.writeUserFields = [];
|
||||
clp.readUserFields = [];
|
||||
|
||||
return clp;
|
||||
}
|
||||
|
||||
function createUserPointer(userId) {
|
||||
return {
|
||||
__type: 'Pointer',
|
||||
className: '_User',
|
||||
objectId: userId,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,644 +0,0 @@
|
||||
const { DefinedSchemas } = require('../lib/SchemaMigrations/DefinedSchemas');
|
||||
const Config = require('../lib/Config');
|
||||
|
||||
const cleanUpIndexes = schema => {
|
||||
if (schema.indexes) {
|
||||
delete schema.indexes._id_;
|
||||
if (!Object.keys(schema.indexes).length) {
|
||||
delete schema.indexes;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
describe('DefinedSchemas', () => {
|
||||
let config;
|
||||
afterEach(async () => {
|
||||
config = Config.get('test');
|
||||
if (config) {
|
||||
await config.database.adapter.deleteAllClasses();
|
||||
}
|
||||
});
|
||||
|
||||
describe('Fields', () => {
|
||||
it('should keep default fields if not provided', async () => {
|
||||
const server = await reconfigureServer();
|
||||
// Will perform create
|
||||
await new DefinedSchemas({ definitions: [{ className: 'Test' }] }, server.config).execute();
|
||||
let schema = await new Parse.Schema('Test').get();
|
||||
const expectedFields = {
|
||||
objectId: { type: 'String' },
|
||||
createdAt: { type: 'Date' },
|
||||
updatedAt: { type: 'Date' },
|
||||
ACL: { type: 'ACL' },
|
||||
};
|
||||
expect(schema.fields).toEqual(expectedFields);
|
||||
|
||||
await server.config.schemaCache.clear();
|
||||
// Will perform update
|
||||
await new DefinedSchemas({ definitions: [{ className: 'Test' }] }, server.config).execute();
|
||||
schema = await new Parse.Schema('Test').get();
|
||||
expect(schema.fields).toEqual(expectedFields);
|
||||
});
|
||||
it('should protect default fields', async () => {
|
||||
const server = await reconfigureServer();
|
||||
|
||||
const schemas = {
|
||||
definitions: [
|
||||
{
|
||||
className: '_User',
|
||||
fields: {
|
||||
email: 'Object',
|
||||
},
|
||||
},
|
||||
{
|
||||
className: '_Role',
|
||||
fields: {
|
||||
users: 'Object',
|
||||
},
|
||||
},
|
||||
{
|
||||
className: '_Installation',
|
||||
fields: {
|
||||
installationId: 'Object',
|
||||
},
|
||||
},
|
||||
{
|
||||
className: 'Test',
|
||||
fields: {
|
||||
createdAt: { type: 'Object' },
|
||||
objectId: { type: 'Number' },
|
||||
updatedAt: { type: 'String' },
|
||||
ACL: { type: 'String' },
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const expectedFields = {
|
||||
objectId: { type: 'String' },
|
||||
createdAt: { type: 'Date' },
|
||||
updatedAt: { type: 'Date' },
|
||||
ACL: { type: 'ACL' },
|
||||
};
|
||||
|
||||
const expectedUserFields = {
|
||||
objectId: { type: 'String' },
|
||||
createdAt: { type: 'Date' },
|
||||
updatedAt: { type: 'Date' },
|
||||
ACL: { type: 'ACL' },
|
||||
username: { type: 'String' },
|
||||
password: { type: 'String' },
|
||||
email: { type: 'String' },
|
||||
emailVerified: { type: 'Boolean' },
|
||||
authData: { type: 'Object' },
|
||||
};
|
||||
|
||||
const expectedRoleFields = {
|
||||
objectId: { type: 'String' },
|
||||
createdAt: { type: 'Date' },
|
||||
updatedAt: { type: 'Date' },
|
||||
ACL: { type: 'ACL' },
|
||||
name: { type: 'String' },
|
||||
users: { type: 'Relation', targetClass: '_User' },
|
||||
roles: { type: 'Relation', targetClass: '_Role' },
|
||||
};
|
||||
|
||||
const expectedInstallationFields = {
|
||||
objectId: { type: 'String' },
|
||||
createdAt: { type: 'Date' },
|
||||
updatedAt: { type: 'Date' },
|
||||
ACL: { type: 'ACL' },
|
||||
installationId: { type: 'String' },
|
||||
deviceToken: { type: 'String' },
|
||||
channels: { type: 'Array' },
|
||||
deviceType: { type: 'String' },
|
||||
pushType: { type: 'String' },
|
||||
GCMSenderId: { type: 'String' },
|
||||
timeZone: { type: 'String' },
|
||||
localeIdentifier: { type: 'String' },
|
||||
badge: { type: 'Number' },
|
||||
appVersion: { type: 'String' },
|
||||
appName: { type: 'String' },
|
||||
appIdentifier: { type: 'String' },
|
||||
parseVersion: { type: 'String' },
|
||||
};
|
||||
|
||||
// Perform create
|
||||
await new DefinedSchemas(schemas, server.config).execute();
|
||||
let schema = await new Parse.Schema('Test').get();
|
||||
expect(schema.fields).toEqual(expectedFields);
|
||||
|
||||
let userSchema = await new Parse.Schema('_User').get();
|
||||
expect(userSchema.fields).toEqual(expectedUserFields);
|
||||
|
||||
let roleSchema = await new Parse.Schema('_Role').get();
|
||||
expect(roleSchema.fields).toEqual(expectedRoleFields);
|
||||
|
||||
let installationSchema = await new Parse.Schema('_Installation').get();
|
||||
expect(installationSchema.fields).toEqual(expectedInstallationFields);
|
||||
|
||||
await server.config.schemaCache.clear();
|
||||
// Perform update
|
||||
await new DefinedSchemas(schemas, server.config).execute();
|
||||
schema = await new Parse.Schema('Test').get();
|
||||
expect(schema.fields).toEqual(expectedFields);
|
||||
|
||||
userSchema = await new Parse.Schema('_User').get();
|
||||
expect(userSchema.fields).toEqual(expectedUserFields);
|
||||
|
||||
roleSchema = await new Parse.Schema('_Role').get();
|
||||
expect(roleSchema.fields).toEqual(expectedRoleFields);
|
||||
|
||||
installationSchema = await new Parse.Schema('_Installation').get();
|
||||
expect(installationSchema.fields).toEqual(expectedInstallationFields);
|
||||
});
|
||||
it('should create new fields', async () => {
|
||||
const server = await reconfigureServer();
|
||||
const fields = {
|
||||
objectId: { type: 'String' },
|
||||
createdAt: { type: 'Date' },
|
||||
updatedAt: { type: 'Date' },
|
||||
ACL: { type: 'ACL' },
|
||||
aString: { type: 'String' },
|
||||
aStringWithDefault: { type: 'String', defaultValue: 'Test' },
|
||||
aStringWithRequired: { type: 'String', required: true },
|
||||
aStringWithRequiredAndDefault: { type: 'String', required: true, defaultValue: 'Test' },
|
||||
aBoolean: { type: 'Boolean' },
|
||||
aFile: { type: 'File' },
|
||||
aNumber: { type: 'Number' },
|
||||
aRelation: { type: 'Relation', targetClass: '_User' },
|
||||
aPointer: { type: 'Pointer', targetClass: '_Role' },
|
||||
aDate: { type: 'Date' },
|
||||
aGeoPoint: { type: 'GeoPoint' },
|
||||
aPolygon: { type: 'Polygon' },
|
||||
aArray: { type: 'Array' },
|
||||
aObject: { type: 'Object' },
|
||||
};
|
||||
const schemas = {
|
||||
definitions: [
|
||||
{
|
||||
className: 'Test',
|
||||
fields,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
// Create
|
||||
await new DefinedSchemas(schemas, server.config).execute();
|
||||
let schema = await new Parse.Schema('Test').get();
|
||||
expect(schema.fields).toEqual(fields);
|
||||
|
||||
fields.anotherObject = { type: 'Object' };
|
||||
// Update
|
||||
await new DefinedSchemas(schemas, server.config).execute();
|
||||
schema = await new Parse.Schema('Test').get();
|
||||
expect(schema.fields).toEqual(fields);
|
||||
});
|
||||
it('should not delete removed fields when "deleteExtraFields" is false', async () => {
|
||||
const server = await reconfigureServer();
|
||||
|
||||
await new DefinedSchemas(
|
||||
{ definitions: [{ className: 'Test', fields: { aField: { type: 'String' } } }] },
|
||||
server.config
|
||||
).execute();
|
||||
|
||||
let schema = await new Parse.Schema('Test').get();
|
||||
expect(schema.fields.aField).toBeDefined();
|
||||
|
||||
await new DefinedSchemas({ definitions: [{ className: 'Test' }] }, server.config).execute();
|
||||
|
||||
schema = await new Parse.Schema('Test').get();
|
||||
expect(schema.fields).toEqual({
|
||||
objectId: { type: 'String' },
|
||||
createdAt: { type: 'Date' },
|
||||
updatedAt: { type: 'Date' },
|
||||
aField: { type: 'String' },
|
||||
ACL: { type: 'ACL' },
|
||||
});
|
||||
});
|
||||
it('should delete removed fields when "deleteExtraFields" is true', async () => {
|
||||
const server = await reconfigureServer();
|
||||
|
||||
await new DefinedSchemas(
|
||||
{
|
||||
definitions: [{ className: 'Test', fields: { aField: { type: 'String' } } }],
|
||||
},
|
||||
server.config
|
||||
).execute();
|
||||
|
||||
let schema = await new Parse.Schema('Test').get();
|
||||
expect(schema.fields.aField).toBeDefined();
|
||||
|
||||
await new DefinedSchemas(
|
||||
{ deleteExtraFields: true, definitions: [{ className: 'Test' }] },
|
||||
server.config
|
||||
).execute();
|
||||
|
||||
schema = await new Parse.Schema('Test').get();
|
||||
expect(schema.fields).toEqual({
|
||||
objectId: { type: 'String' },
|
||||
createdAt: { type: 'Date' },
|
||||
updatedAt: { type: 'Date' },
|
||||
ACL: { type: 'ACL' },
|
||||
});
|
||||
});
|
||||
it('should re create fields with changed type when "recreateModifiedFields" is true', async () => {
|
||||
const server = await reconfigureServer();
|
||||
|
||||
await new DefinedSchemas(
|
||||
{ definitions: [{ className: 'Test', fields: { aField: { type: 'String' } } }] },
|
||||
server.config
|
||||
).execute();
|
||||
|
||||
let schema = await new Parse.Schema('Test').get();
|
||||
expect(schema.fields.aField).toEqual({ type: 'String' });
|
||||
|
||||
const object = new Parse.Object('Test');
|
||||
await object.save({ aField: 'Hello' }, { useMasterKey: true });
|
||||
|
||||
await new DefinedSchemas(
|
||||
{
|
||||
recreateModifiedFields: true,
|
||||
definitions: [{ className: 'Test', fields: { aField: { type: 'Number' } } }],
|
||||
},
|
||||
server.config
|
||||
).execute();
|
||||
|
||||
schema = await new Parse.Schema('Test').get();
|
||||
expect(schema.fields.aField).toEqual({ type: 'Number' });
|
||||
|
||||
await object.fetch({ useMasterKey: true });
|
||||
expect(object.get('aField')).toBeUndefined();
|
||||
});
|
||||
it('should not re create fields with changed type when "recreateModifiedFields" is not true', async () => {
|
||||
const server = await reconfigureServer();
|
||||
|
||||
await new DefinedSchemas(
|
||||
{ definitions: [{ className: 'Test', fields: { aField: { type: 'String' } } }] },
|
||||
server.config
|
||||
).execute();
|
||||
|
||||
let schema = await new Parse.Schema('Test').get();
|
||||
expect(schema.fields.aField).toEqual({ type: 'String' });
|
||||
|
||||
const object = new Parse.Object('Test');
|
||||
await object.save({ aField: 'Hello' }, { useMasterKey: true });
|
||||
|
||||
await new DefinedSchemas(
|
||||
{ definitions: [{ className: 'Test', fields: { aField: { type: 'Number' } } }] },
|
||||
server.config
|
||||
).execute();
|
||||
|
||||
schema = await new Parse.Schema('Test').get();
|
||||
expect(schema.fields.aField).toEqual({ type: 'String' });
|
||||
|
||||
await object.fetch({ useMasterKey: true });
|
||||
expect(object.get('aField')).toBeDefined();
|
||||
});
|
||||
it('should just update classic fields with changed params', async () => {
|
||||
const server = await reconfigureServer();
|
||||
|
||||
await new DefinedSchemas(
|
||||
{ definitions: [{ className: 'Test', fields: { aField: { type: 'String' } } }] },
|
||||
server.config
|
||||
).execute();
|
||||
|
||||
let schema = await new Parse.Schema('Test').get();
|
||||
expect(schema.fields.aField).toEqual({ type: 'String' });
|
||||
|
||||
const object = new Parse.Object('Test');
|
||||
await object.save({ aField: 'Hello' }, { useMasterKey: true });
|
||||
|
||||
await new DefinedSchemas(
|
||||
{
|
||||
definitions: [
|
||||
{ className: 'Test', fields: { aField: { type: 'String', required: true } } },
|
||||
],
|
||||
},
|
||||
server.config
|
||||
).execute();
|
||||
|
||||
schema = await new Parse.Schema('Test').get();
|
||||
expect(schema.fields.aField).toEqual({ type: 'String', required: true });
|
||||
|
||||
await object.fetch({ useMasterKey: true });
|
||||
expect(object.get('aField')).toEqual('Hello');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Indexes', () => {
|
||||
it('should create new indexes', async () => {
|
||||
const server = await reconfigureServer();
|
||||
|
||||
const indexes = { complex: { createdAt: 1, updatedAt: 1 } };
|
||||
|
||||
const schemas = {
|
||||
definitions: [{ className: 'Test', fields: { aField: { type: 'String' } }, indexes }],
|
||||
};
|
||||
await new DefinedSchemas(schemas, server.config).execute();
|
||||
|
||||
let schema = await new Parse.Schema('Test').get();
|
||||
cleanUpIndexes(schema);
|
||||
expect(schema.indexes).toEqual(indexes);
|
||||
|
||||
indexes.complex2 = { createdAt: 1, aField: 1 };
|
||||
await new DefinedSchemas(schemas, server.config).execute();
|
||||
schema = await new Parse.Schema('Test').get();
|
||||
cleanUpIndexes(schema);
|
||||
expect(schema.indexes).toEqual(indexes);
|
||||
});
|
||||
it('should re create changed indexes', async () => {
|
||||
const server = await reconfigureServer();
|
||||
|
||||
let indexes = { complex: { createdAt: 1, updatedAt: 1 } };
|
||||
|
||||
let schemas = { definitions: [{ className: 'Test', indexes }] };
|
||||
await new DefinedSchemas(schemas, server.config).execute();
|
||||
|
||||
indexes = { complex: { createdAt: 1 } };
|
||||
schemas = { definitions: [{ className: 'Test', indexes }] };
|
||||
|
||||
// Change indexes
|
||||
await new DefinedSchemas(schemas, server.config).execute();
|
||||
let schema = await new Parse.Schema('Test').get();
|
||||
cleanUpIndexes(schema);
|
||||
expect(schema.indexes).toEqual(indexes);
|
||||
|
||||
// Update
|
||||
await new DefinedSchemas(schemas, server.config).execute();
|
||||
schema = await new Parse.Schema('Test').get();
|
||||
cleanUpIndexes(schema);
|
||||
expect(schema.indexes).toEqual(indexes);
|
||||
});
|
||||
|
||||
it('should delete removed indexes', async () => {
|
||||
const server = await reconfigureServer();
|
||||
|
||||
let indexes = { complex: { createdAt: 1, updatedAt: 1 } };
|
||||
|
||||
let schemas = { definitions: [{ className: 'Test', indexes }] };
|
||||
await new DefinedSchemas(schemas, server.config).execute();
|
||||
|
||||
indexes = {};
|
||||
schemas = { definitions: [{ className: 'Test', indexes }] };
|
||||
// Change indexes
|
||||
await new DefinedSchemas(schemas, server.config).execute();
|
||||
let schema = await new Parse.Schema('Test').get();
|
||||
cleanUpIndexes(schema);
|
||||
expect(schema.indexes).toBeUndefined();
|
||||
|
||||
// Update
|
||||
await new DefinedSchemas(schemas, server.config).execute();
|
||||
schema = await new Parse.Schema('Test').get();
|
||||
cleanUpIndexes(schema);
|
||||
expect(schema.indexes).toBeUndefined();
|
||||
});
|
||||
xit('should keep protected indexes', async () => {
|
||||
const server = await reconfigureServer();
|
||||
|
||||
const expectedIndexes = {
|
||||
username_1: { username: 1 },
|
||||
case_insensitive_username: { username: 1 },
|
||||
email_1: { email: 1 },
|
||||
case_insensitive_email: { email: 1 },
|
||||
};
|
||||
const schemas = {
|
||||
definitions: [
|
||||
{
|
||||
className: '_User',
|
||||
indexes: {
|
||||
case_insensitive_username: { password: true },
|
||||
case_insensitive_email: { password: true },
|
||||
},
|
||||
},
|
||||
{ className: 'Test' },
|
||||
],
|
||||
};
|
||||
// Create
|
||||
await new DefinedSchemas(schemas, server.config).execute();
|
||||
let userSchema = await new Parse.Schema('_User').get();
|
||||
let testSchema = await new Parse.Schema('Test').get();
|
||||
cleanUpIndexes(userSchema);
|
||||
cleanUpIndexes(testSchema);
|
||||
expect(testSchema.indexes).toBeUndefined();
|
||||
expect(userSchema.indexes).toEqual(expectedIndexes);
|
||||
|
||||
// Update
|
||||
await new DefinedSchemas(schemas, server.config).execute();
|
||||
userSchema = await new Parse.Schema('_User').get();
|
||||
testSchema = await new Parse.Schema('Test').get();
|
||||
cleanUpIndexes(userSchema);
|
||||
cleanUpIndexes(testSchema);
|
||||
expect(testSchema.indexes).toBeUndefined();
|
||||
expect(userSchema.indexes).toEqual(expectedIndexes);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ClassLevelPermissions', () => {
|
||||
it('should use default CLP', async () => {
|
||||
const server = await reconfigureServer();
|
||||
const schemas = { definitions: [{ className: 'Test' }] };
|
||||
await new DefinedSchemas(schemas, server.config).execute();
|
||||
|
||||
const expectedTestCLP = {
|
||||
find: {},
|
||||
count: {},
|
||||
get: {},
|
||||
create: {},
|
||||
update: {},
|
||||
delete: {},
|
||||
addField: {},
|
||||
protectedFields: {},
|
||||
};
|
||||
let testSchema = await new Parse.Schema('Test').get();
|
||||
expect(testSchema.classLevelPermissions).toEqual(expectedTestCLP);
|
||||
|
||||
await new DefinedSchemas(schemas, server.config).execute();
|
||||
testSchema = await new Parse.Schema('Test').get();
|
||||
expect(testSchema.classLevelPermissions).toEqual(expectedTestCLP);
|
||||
});
|
||||
it('should save CLP', async () => {
|
||||
const server = await reconfigureServer();
|
||||
|
||||
const expectedTestCLP = {
|
||||
find: {},
|
||||
count: { requiresAuthentication: true },
|
||||
get: { 'role:Admin': true },
|
||||
create: { 'role:ARole': true, requiresAuthentication: true },
|
||||
update: { requiresAuthentication: true },
|
||||
delete: { requiresAuthentication: true },
|
||||
addField: {},
|
||||
protectedFields: { '*': ['aField'], 'role:Admin': ['anotherField'] },
|
||||
};
|
||||
const schemas = {
|
||||
definitions: [
|
||||
{
|
||||
className: 'Test',
|
||||
fields: { aField: { type: 'String' }, anotherField: { type: 'Object' } },
|
||||
classLevelPermissions: expectedTestCLP,
|
||||
},
|
||||
],
|
||||
};
|
||||
await new DefinedSchemas(schemas, server.config).execute();
|
||||
|
||||
let testSchema = await new Parse.Schema('Test').get();
|
||||
expect(testSchema.classLevelPermissions).toEqual(expectedTestCLP);
|
||||
|
||||
expectedTestCLP.update = {};
|
||||
expectedTestCLP.create = { requiresAuthentication: true };
|
||||
|
||||
await new DefinedSchemas(schemas, server.config).execute();
|
||||
testSchema = await new Parse.Schema('Test').get();
|
||||
expect(testSchema.classLevelPermissions).toEqual(expectedTestCLP);
|
||||
});
|
||||
it('should force addField to empty', async () => {
|
||||
const server = await reconfigureServer();
|
||||
const schemas = {
|
||||
definitions: [{ className: 'Test', classLevelPermissions: { addField: { '*': true } } }],
|
||||
};
|
||||
await new DefinedSchemas(schemas, server.config).execute();
|
||||
|
||||
const expectedTestCLP = {
|
||||
find: {},
|
||||
count: {},
|
||||
get: {},
|
||||
create: {},
|
||||
update: {},
|
||||
delete: {},
|
||||
addField: {},
|
||||
protectedFields: {},
|
||||
};
|
||||
|
||||
let testSchema = await new Parse.Schema('Test').get();
|
||||
expect(testSchema.classLevelPermissions).toEqual(expectedTestCLP);
|
||||
|
||||
await new DefinedSchemas(schemas, server.config).execute();
|
||||
testSchema = await new Parse.Schema('Test').get();
|
||||
expect(testSchema.classLevelPermissions).toEqual(expectedTestCLP);
|
||||
});
|
||||
});
|
||||
|
||||
it('should not delete automatically classes', async () => {
|
||||
await reconfigureServer({
|
||||
schema: { definitions: [{ className: '_User' }, { className: 'Test' }] },
|
||||
});
|
||||
|
||||
await reconfigureServer({ schema: { definitions: [{ className: '_User' }] } });
|
||||
|
||||
const schema = await new Parse.Schema('Test').get();
|
||||
expect(schema.className).toEqual('Test');
|
||||
});
|
||||
|
||||
it('should disable class PUT/POST endpoint when lockSchemas provided to avoid dual source of truth', async () => {
|
||||
await reconfigureServer({
|
||||
schema: {
|
||||
lockSchemas: true,
|
||||
definitions: [{ className: '_User' }, { className: 'Test' }],
|
||||
},
|
||||
});
|
||||
|
||||
const schema = await new Parse.Schema('Test').get();
|
||||
expect(schema.className).toEqual('Test');
|
||||
|
||||
const schemas = await Parse.Schema.all();
|
||||
// Role could be flaky since all system classes are not ensured
|
||||
// at start up by the DefinedSchema system
|
||||
expect(schemas.filter(({ className }) => className !== '_Role').length).toEqual(3);
|
||||
|
||||
await expectAsync(new Parse.Schema('TheNewTest').save()).toBeRejectedWithError(
|
||||
'Cannot perform this operation when schemas options is used.'
|
||||
);
|
||||
|
||||
await expectAsync(new Parse.Schema('_User').update()).toBeRejectedWithError(
|
||||
'Cannot perform this operation when schemas options is used.'
|
||||
);
|
||||
});
|
||||
it('should only enable delete class endpoint since', async () => {
|
||||
await reconfigureServer({
|
||||
schema: { definitions: [{ className: '_User' }, { className: 'Test' }] },
|
||||
});
|
||||
await reconfigureServer({ schema: { definitions: [{ className: '_User' }] } });
|
||||
|
||||
let schemas = await Parse.Schema.all();
|
||||
expect(schemas.length).toEqual(4);
|
||||
|
||||
await new Parse.Schema('_User').delete();
|
||||
schemas = await Parse.Schema.all();
|
||||
expect(schemas.length).toEqual(3);
|
||||
});
|
||||
it('should run beforeMigration before execution of DefinedSchemas', async () => {
|
||||
const config = {
|
||||
schema: {
|
||||
definitions: [{ className: '_User' }, { className: 'Test' }],
|
||||
beforeMigration: async () => {},
|
||||
},
|
||||
};
|
||||
const spy = spyOn(config.schema, 'beforeMigration');
|
||||
await reconfigureServer(config);
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
it('should run afterMigration after execution of DefinedSchemas', async () => {
|
||||
const config = {
|
||||
schema: {
|
||||
definitions: [{ className: '_User' }, { className: 'Test' }],
|
||||
afterMigration: async () => {},
|
||||
},
|
||||
};
|
||||
const spy = spyOn(config.schema, 'afterMigration');
|
||||
await reconfigureServer(config);
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should use logger in case of error', async () => {
|
||||
const server = await reconfigureServer({ schema: { definitions: [{ className: '_User' }] } });
|
||||
const error = new Error('A test error');
|
||||
const logger = require('../lib/logger').logger;
|
||||
spyOn(DefinedSchemas.prototype, 'wait').and.resolveTo();
|
||||
spyOn(logger, 'error').and.callThrough();
|
||||
spyOn(Parse.Schema, 'all').and.callFake(() => {
|
||||
throw error;
|
||||
});
|
||||
|
||||
await new DefinedSchemas(
|
||||
{ definitions: [{ className: 'Test', fields: { aField: { type: 'String' } } }] },
|
||||
server.config
|
||||
).execute();
|
||||
|
||||
expect(logger.error).toHaveBeenCalledWith(`Failed to run migrations: ${error.toString()}`);
|
||||
});
|
||||
it('should perform migration in parallel without failing', async () => {
|
||||
const server = await reconfigureServer();
|
||||
const logger = require('../lib/logger').logger;
|
||||
spyOn(logger, 'error').and.callThrough();
|
||||
const migrationOptions = {
|
||||
definitions: [
|
||||
{
|
||||
className: 'Test',
|
||||
fields: { aField: { type: 'String' } },
|
||||
indexes: { aField: { aField: 1 } },
|
||||
classLevelPermissions: {
|
||||
create: { requiresAuthentication: true },
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
// Simulate parallel deployment
|
||||
await Promise.all([
|
||||
new DefinedSchemas(migrationOptions, server.config).execute(),
|
||||
new DefinedSchemas(migrationOptions, server.config).execute(),
|
||||
new DefinedSchemas(migrationOptions, server.config).execute(),
|
||||
new DefinedSchemas(migrationOptions, server.config).execute(),
|
||||
new DefinedSchemas(migrationOptions, server.config).execute(),
|
||||
]);
|
||||
|
||||
const testSchema = (await Parse.Schema.all()).find(
|
||||
({ className }) => className === migrationOptions.definitions[0].className
|
||||
);
|
||||
|
||||
expect(testSchema.indexes.aField).toEqual({ aField: 1 });
|
||||
expect(testSchema.fields.aField).toEqual({ type: 'String' });
|
||||
expect(testSchema.classLevelPermissions.create).toEqual({ requiresAuthentication: true });
|
||||
expect(logger.error).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
});
|
||||
@@ -1,48 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const Deprecator = require('../lib/Deprecator/Deprecator');
|
||||
|
||||
describe('Deprecator', () => {
|
||||
let deprecations = [];
|
||||
|
||||
beforeEach(async () => {
|
||||
deprecations = [{ optionKey: 'exampleKey', changeNewDefault: 'exampleNewDefault' }];
|
||||
});
|
||||
|
||||
it('deprecations are an array', async () => {
|
||||
expect(Deprecator._getDeprecations()).toBeInstanceOf(Array);
|
||||
});
|
||||
|
||||
it('logs deprecation for new default', async () => {
|
||||
deprecations = [{ optionKey: 'exampleKey', changeNewDefault: 'exampleNewDefault' }];
|
||||
|
||||
spyOn(Deprecator, '_getDeprecations').and.callFake(() => deprecations);
|
||||
const logger = require('../lib/logger').logger;
|
||||
const logSpy = spyOn(logger, 'warn').and.callFake(() => {});
|
||||
|
||||
await reconfigureServer();
|
||||
expect(logSpy.calls.all()[0].args[0]).toEqual(
|
||||
`DeprecationWarning: The Parse Server option '${deprecations[0].optionKey}' default will change to '${deprecations[0].changeNewDefault}' in a future version.`
|
||||
);
|
||||
});
|
||||
|
||||
it('does not log deprecation for new default if option is set manually', async () => {
|
||||
deprecations = [{ optionKey: 'exampleKey', changeNewDefault: 'exampleNewDefault' }];
|
||||
|
||||
spyOn(Deprecator, '_getDeprecations').and.callFake(() => deprecations);
|
||||
const logSpy = spyOn(Deprecator, '_logOption').and.callFake(() => {});
|
||||
await reconfigureServer({ [deprecations[0].optionKey]: 'manuallySet' });
|
||||
expect(logSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('logs runtime deprecation', async () => {
|
||||
const logger = require('../lib/logger').logger;
|
||||
const logSpy = spyOn(logger, 'warn').and.callFake(() => {});
|
||||
const options = { usage: 'Doing this', solution: 'Do that instead.' };
|
||||
|
||||
Deprecator.logRuntimeDeprecation(options);
|
||||
expect(logSpy.calls.all()[0].args[0]).toEqual(
|
||||
`DeprecationWarning: ${options.usage} is deprecated and will be removed in a future version. ${options.solution}`
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -31,6 +31,7 @@ describe('Email Verification Token Expiration: ', () => {
|
||||
// wait for 1 second - simulate user behavior to some extent
|
||||
setTimeout(() => {
|
||||
expect(sendEmailOptions).not.toBeUndefined();
|
||||
console.log(sendEmailOptions.link);
|
||||
|
||||
request({
|
||||
url: sendEmailOptions.link,
|
||||
@@ -324,7 +325,9 @@ describe('Email Verification Token Expiration: ', () => {
|
||||
expect(typeof user).toBe('object');
|
||||
expect(user.emailVerified).toEqual(true);
|
||||
expect(typeof user._email_verify_token).toBe('undefined');
|
||||
expect(typeof user._email_verify_token_expires_at).toBe('undefined');
|
||||
expect(typeof user._email_verify_token_expires_at).toBe(
|
||||
'undefined'
|
||||
);
|
||||
done();
|
||||
})
|
||||
.catch(error => {
|
||||
@@ -510,7 +513,7 @@ describe('Email Verification Token Expiration: ', () => {
|
||||
userAfterEmailReset._email_verify_token
|
||||
);
|
||||
expect(userBeforeEmailReset._email_verify_token_expires_at).not.toEqual(
|
||||
userAfterEmailReset._email_verify_token_expires_at
|
||||
userAfterEmailReset.__email_verify_token_expires_at
|
||||
);
|
||||
expect(sendEmailOptions).toBeDefined();
|
||||
done();
|
||||
@@ -594,7 +597,7 @@ describe('Email Verification Token Expiration: ', () => {
|
||||
userAfterRequest._email_verify_token
|
||||
);
|
||||
expect(userBeforeRequest._email_verify_token_expires_at).not.toEqual(
|
||||
userAfterRequest._email_verify_token_expires_at
|
||||
userAfterRequest.__email_verify_token_expires_at
|
||||
);
|
||||
done();
|
||||
})
|
||||
@@ -604,110 +607,6 @@ describe('Email Verification Token Expiration: ', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw with invalid emailVerifyTokenReuseIfValid', async done => {
|
||||
const sendEmailOptions = [];
|
||||
const emailAdapter = {
|
||||
sendVerificationEmail: () => Promise.resolve(),
|
||||
sendPasswordResetEmail: options => {
|
||||
sendEmailOptions.push(options);
|
||||
},
|
||||
sendMail: () => {},
|
||||
};
|
||||
try {
|
||||
await reconfigureServer({
|
||||
appName: 'passwordPolicy',
|
||||
verifyUserEmails: true,
|
||||
emailAdapter: emailAdapter,
|
||||
emailVerifyTokenValidityDuration: 5 * 60, // 5 minutes
|
||||
emailVerifyTokenReuseIfValid: [],
|
||||
publicServerURL: 'http://localhost:8378/1',
|
||||
});
|
||||
fail('should have thrown.');
|
||||
} catch (e) {
|
||||
expect(e).toBe('emailVerifyTokenReuseIfValid must be a boolean value');
|
||||
}
|
||||
try {
|
||||
await reconfigureServer({
|
||||
appName: 'passwordPolicy',
|
||||
verifyUserEmails: true,
|
||||
emailAdapter: emailAdapter,
|
||||
emailVerifyTokenReuseIfValid: true,
|
||||
publicServerURL: 'http://localhost:8378/1',
|
||||
});
|
||||
fail('should have thrown.');
|
||||
} catch (e) {
|
||||
expect(e).toBe(
|
||||
'You cannot use emailVerifyTokenReuseIfValid without emailVerifyTokenValidityDuration'
|
||||
);
|
||||
}
|
||||
done();
|
||||
});
|
||||
|
||||
it('should match codes with emailVerifyTokenReuseIfValid', async done => {
|
||||
let sendEmailOptions;
|
||||
let sendVerificationEmailCallCount = 0;
|
||||
const emailAdapter = {
|
||||
sendVerificationEmail: options => {
|
||||
sendEmailOptions = options;
|
||||
sendVerificationEmailCallCount++;
|
||||
},
|
||||
sendPasswordResetEmail: () => Promise.resolve(),
|
||||
sendMail: () => {},
|
||||
};
|
||||
await reconfigureServer({
|
||||
appName: 'emailVerifyToken',
|
||||
verifyUserEmails: true,
|
||||
emailAdapter: emailAdapter,
|
||||
emailVerifyTokenValidityDuration: 5 * 60, // 5 minutes
|
||||
publicServerURL: 'http://localhost:8378/1',
|
||||
emailVerifyTokenReuseIfValid: true,
|
||||
});
|
||||
const user = new Parse.User();
|
||||
user.setUsername('resends_verification_token');
|
||||
user.setPassword('expiringToken');
|
||||
user.set('email', 'user@example.com');
|
||||
await user.signUp();
|
||||
|
||||
const config = Config.get('test');
|
||||
const [userBeforeRequest] = await config.database.find('_User', {
|
||||
username: 'resends_verification_token',
|
||||
});
|
||||
// store this user before we make our email request
|
||||
expect(sendVerificationEmailCallCount).toBe(1);
|
||||
await new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
resolve();
|
||||
}, 1000);
|
||||
});
|
||||
const response = await request({
|
||||
url: 'http://localhost:8378/1/verificationEmailRequest',
|
||||
method: 'POST',
|
||||
body: {
|
||||
email: 'user@example.com',
|
||||
},
|
||||
headers: {
|
||||
'X-Parse-Application-Id': Parse.applicationId,
|
||||
'X-Parse-REST-API-Key': 'rest',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
expect(response.status).toBe(200);
|
||||
expect(sendVerificationEmailCallCount).toBe(2);
|
||||
expect(sendEmailOptions).toBeDefined();
|
||||
|
||||
const [userAfterRequest] = await config.database.find('_User', {
|
||||
username: 'resends_verification_token',
|
||||
});
|
||||
|
||||
// verify that our token & expiration has been changed for this new request
|
||||
expect(typeof userAfterRequest).toBe('object');
|
||||
expect(userBeforeRequest._email_verify_token).toEqual(userAfterRequest._email_verify_token);
|
||||
expect(userBeforeRequest._email_verify_token_expires_at).toEqual(
|
||||
userAfterRequest._email_verify_token_expires_at
|
||||
);
|
||||
done();
|
||||
});
|
||||
|
||||
it('should not send a new verification email when a resend is requested and the user is VERIFIED', done => {
|
||||
const user = new Parse.User();
|
||||
let sendEmailOptions;
|
||||
@@ -893,7 +792,9 @@ describe('Email Verification Token Expiration: ', () => {
|
||||
.then(response => {
|
||||
expect(response.status).toBe(400);
|
||||
expect(response.data.code).toBe(Parse.Error.INVALID_EMAIL_ADDRESS);
|
||||
expect(response.data.error).toBe('you must provide a valid email string');
|
||||
expect(response.data.error).toBe(
|
||||
'you must provide a valid email string'
|
||||
);
|
||||
expect(sendVerificationEmailCallCount).toBe(0);
|
||||
expect(sendEmailOptions).not.toBeDefined();
|
||||
done();
|
||||
@@ -933,7 +834,9 @@ describe('Email Verification Token Expiration: ', () => {
|
||||
.fetch()
|
||||
.then(() => {
|
||||
expect(user.get('emailVerified')).toEqual(false);
|
||||
expect(typeof user.get('_email_verify_token_expires_at')).toBe('undefined');
|
||||
expect(typeof user.get('_email_verify_token_expires_at')).toBe(
|
||||
'undefined'
|
||||
);
|
||||
expect(sendEmailOptions).toBeDefined();
|
||||
done();
|
||||
})
|
||||
|
||||
@@ -1,34 +1,63 @@
|
||||
const ParseServer = require('../lib/index');
|
||||
const express = require('express');
|
||||
const request = require('../lib/request');
|
||||
|
||||
describe('Enable express error handler', () => {
|
||||
it('should call the default handler in case of error, like updating a non existing object', async done => {
|
||||
spyOn(console, 'error');
|
||||
const parseServer = await reconfigureServer(
|
||||
it('should call the default handler in case of error, like updating a non existing object', done => {
|
||||
const serverUrl = 'http://localhost:12667/parse';
|
||||
const appId = 'anOtherTestApp';
|
||||
const masterKey = 'anOtherTestMasterKey';
|
||||
let server;
|
||||
|
||||
let lastError;
|
||||
|
||||
const parseServer = ParseServer.ParseServer(
|
||||
Object.assign({}, defaultConfiguration, {
|
||||
appId: appId,
|
||||
masterKey: masterKey,
|
||||
serverURL: serverUrl,
|
||||
enableExpressErrorHandler: true,
|
||||
serverStartComplete: () => {
|
||||
expect(Parse.applicationId).toEqual('anOtherTestApp');
|
||||
const app = express();
|
||||
app.use('/parse', parseServer);
|
||||
|
||||
server = app.listen(12667);
|
||||
|
||||
app.use(function(err, req, res, next) {
|
||||
next;
|
||||
lastError = err;
|
||||
});
|
||||
|
||||
request({
|
||||
method: 'PUT',
|
||||
url: serverUrl + '/classes/AnyClass/nonExistingId',
|
||||
headers: {
|
||||
'X-Parse-Application-Id': appId,
|
||||
'X-Parse-Master-Key': masterKey,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: { someField: 'blablabla' },
|
||||
})
|
||||
.then(() => {
|
||||
fail('Should throw error');
|
||||
})
|
||||
.catch(response => {
|
||||
const reqError = response.data;
|
||||
expect(reqError).toBeDefined();
|
||||
expect(lastError).toBeDefined();
|
||||
|
||||
expect(lastError.code).toEqual(101);
|
||||
expect(lastError.message).toEqual('Object not found.');
|
||||
|
||||
expect(lastError.code).toEqual(reqError.code);
|
||||
expect(lastError.message).toEqual(reqError.error);
|
||||
})
|
||||
.then(() => {
|
||||
server.close(done);
|
||||
});
|
||||
},
|
||||
})
|
||||
);
|
||||
parseServer.app.use(function (err, req, res, next) {
|
||||
expect(err.message).toBe('Object not found.');
|
||||
next(err);
|
||||
});
|
||||
|
||||
try {
|
||||
await request({
|
||||
method: 'PUT',
|
||||
url: defaultConfiguration.serverURL + '/classes/AnyClass/nonExistingId',
|
||||
headers: {
|
||||
'X-Parse-Application-Id': defaultConfiguration.appId,
|
||||
'X-Parse-Master-Key': defaultConfiguration.masterKey,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: { someField: 'blablabla' },
|
||||
});
|
||||
fail('Should throw error');
|
||||
} catch (response) {
|
||||
expect(response).toBeDefined();
|
||||
expect(response.status).toEqual(500);
|
||||
parseServer.server.close(done);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
const auth = require('../lib/Auth');
|
||||
const Config = require('../lib/Config');
|
||||
const rest = require('../lib/rest');
|
||||
|
||||
describe('Enable single schema cache', () => {
|
||||
beforeEach(done => {
|
||||
reconfigureServer({
|
||||
enableSingleSchemaCache: true,
|
||||
schemaCacheTTL: 30000,
|
||||
}).then(() => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('can perform multiple create and query operations', done => {
|
||||
let config = fakeRequestForConfig();
|
||||
let nobody = auth.nobody(config);
|
||||
rest
|
||||
.create(config, nobody, 'Foo', { type: 1 })
|
||||
.then(() => {
|
||||
config = fakeRequestForConfig();
|
||||
nobody = auth.nobody(config);
|
||||
return rest.create(config, nobody, 'Foo', { type: 2 });
|
||||
})
|
||||
.then(() => {
|
||||
config = fakeRequestForConfig();
|
||||
nobody = auth.nobody(config);
|
||||
return rest.create(config, nobody, 'Bar');
|
||||
})
|
||||
.then(() => {
|
||||
config = fakeRequestForConfig();
|
||||
nobody = auth.nobody(config);
|
||||
return rest.find(config, nobody, 'Bar', { type: 1 });
|
||||
})
|
||||
.then(
|
||||
() => {
|
||||
fail('Should throw error');
|
||||
done();
|
||||
},
|
||||
error => {
|
||||
config = fakeRequestForConfig();
|
||||
nobody = auth.nobody(config);
|
||||
expect(error).toBeDefined();
|
||||
return rest.find(config, nobody, 'Foo', { type: 1 });
|
||||
}
|
||||
)
|
||||
.then(response => {
|
||||
config = fakeRequestForConfig();
|
||||
nobody = auth.nobody(config);
|
||||
expect(response.results.length).toEqual(1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const fakeRequestForConfig = function() {
|
||||
return Config.get('test');
|
||||
};
|
||||
@@ -1,13 +1,14 @@
|
||||
const EventEmitterPubSub = require('../lib/Adapters/PubSub/EventEmitterPubSub').EventEmitterPubSub;
|
||||
const EventEmitterPubSub = require('../lib/Adapters/PubSub/EventEmitterPubSub')
|
||||
.EventEmitterPubSub;
|
||||
|
||||
describe('EventEmitterPubSub', function () {
|
||||
it('can publish and subscribe', function () {
|
||||
describe('EventEmitterPubSub', function() {
|
||||
it('can publish and subscribe', function() {
|
||||
const publisher = EventEmitterPubSub.createPublisher();
|
||||
const subscriber = EventEmitterPubSub.createSubscriber();
|
||||
subscriber.subscribe('testChannel');
|
||||
// Register mock checked for subscriber
|
||||
let isChecked = false;
|
||||
subscriber.on('message', function (channel, message) {
|
||||
subscriber.on('message', function(channel, message) {
|
||||
isChecked = true;
|
||||
expect(channel).toBe('testChannel');
|
||||
expect(message).toBe('testMessage');
|
||||
@@ -18,14 +19,14 @@ describe('EventEmitterPubSub', function () {
|
||||
expect(isChecked).toBe(true);
|
||||
});
|
||||
|
||||
it('can unsubscribe', function () {
|
||||
it('can unsubscribe', function() {
|
||||
const publisher = EventEmitterPubSub.createPublisher();
|
||||
const subscriber = EventEmitterPubSub.createSubscriber();
|
||||
subscriber.subscribe('testChannel');
|
||||
subscriber.unsubscribe('testChannel');
|
||||
// Register mock checked for subscriber
|
||||
let isCalled = false;
|
||||
subscriber.on('message', function () {
|
||||
subscriber.on('message', function() {
|
||||
isCalled = true;
|
||||
});
|
||||
|
||||
@@ -34,7 +35,7 @@ describe('EventEmitterPubSub', function () {
|
||||
expect(isCalled).toBe(false);
|
||||
});
|
||||
|
||||
it('can unsubscribe not subscribing channel', function () {
|
||||
it('can unsubscribe not subscribing channel', function() {
|
||||
const subscriber = EventEmitterPubSub.createSubscriber();
|
||||
|
||||
// Make sure subscriber does not throw exception
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
const LoggerController = require('../lib/Controllers/LoggerController').LoggerController;
|
||||
const LoggerController = require('../lib/Controllers/LoggerController')
|
||||
.LoggerController;
|
||||
const WinstonLoggerAdapter = require('../lib/Adapters/Logger/WinstonLoggerAdapter')
|
||||
.WinstonLoggerAdapter;
|
||||
const GridFSBucketAdapter = require('../lib/Adapters/Files/GridFSBucketAdapter')
|
||||
.GridFSBucketAdapter;
|
||||
const Config = require('../lib/Config');
|
||||
const FilesController = require('../lib/Controllers/FilesController').default;
|
||||
const databaseURI = 'mongodb://localhost:27017/parse';
|
||||
|
||||
const mockAdapter = {
|
||||
createFile: () => {
|
||||
@@ -14,18 +14,17 @@ const mockAdapter = {
|
||||
deleteFile: () => {},
|
||||
getFileData: () => {},
|
||||
getFileLocation: () => 'xyz',
|
||||
validateFilename: () => {
|
||||
return null;
|
||||
},
|
||||
};
|
||||
|
||||
// Small additional tests to improve overall coverage
|
||||
describe('FilesController', () => {
|
||||
it('should properly expand objects', done => {
|
||||
const config = Config.get(Parse.applicationId);
|
||||
const gridFSAdapter = new GridFSBucketAdapter('mongodb://localhost:27017/parse');
|
||||
const filesController = new FilesController(gridFSAdapter);
|
||||
const result = filesController.expandFilesInObject(config, function () {});
|
||||
const gridStoreAdapter = new GridFSBucketAdapter(
|
||||
'mongodb://localhost:27017/parse'
|
||||
);
|
||||
const filesController = new FilesController(gridStoreAdapter);
|
||||
const result = filesController.expandFilesInObject(config, function() {});
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
|
||||
@@ -43,20 +42,6 @@ describe('FilesController', () => {
|
||||
done();
|
||||
});
|
||||
|
||||
it_only_db('mongo')('should pass databaseOptions to GridFSBucketAdapter', async () => {
|
||||
await reconfigureServer({
|
||||
databaseURI: 'mongodb://localhost:27017/parse',
|
||||
filesAdapter: null,
|
||||
databaseAdapter: null,
|
||||
databaseOptions: {
|
||||
retryWrites: true,
|
||||
},
|
||||
});
|
||||
const config = Config.get(Parse.applicationId);
|
||||
expect(config.database.adapter._mongoOptions.retryWrites).toBeTrue();
|
||||
expect(config.filesController.adapter._mongoOptions.retryWrites).toBeTrue();
|
||||
});
|
||||
|
||||
it('should create a server log on failure', done => {
|
||||
const logController = new LoggerController(new WinstonLoggerAdapter());
|
||||
|
||||
@@ -67,15 +52,21 @@ describe('FilesController', () => {
|
||||
() => setImmediate(() => Promise.resolve('done'))
|
||||
)
|
||||
.then(() => new Promise(resolve => setTimeout(resolve, 200)))
|
||||
.then(() => logController.getLogs({ from: Date.now() - 1000, size: 1000 }))
|
||||
.then(() =>
|
||||
logController.getLogs({ from: Date.now() - 1000, size: 1000 })
|
||||
)
|
||||
.then(logs => {
|
||||
// we get two logs here: 1. the source of the failure to save the file
|
||||
// and 2 the message that will be sent back to the client.
|
||||
|
||||
const log1 = logs.find(x => x.message === 'Error creating a file: it failed with xyz');
|
||||
const log1 = logs.find(
|
||||
x => x.message === 'Error creating a file: it failed with xyz'
|
||||
);
|
||||
expect(log1.level).toBe('error');
|
||||
|
||||
const log2 = logs.find(x => x.message === 'it failed with xyz');
|
||||
const log2 = logs.find(
|
||||
x => x.message === 'Parse error: Could not store file: yolo.txt.'
|
||||
);
|
||||
expect(log2.level).toBe('error');
|
||||
expect(log2.code).toBe(130);
|
||||
|
||||
@@ -83,37 +74,23 @@ describe('FilesController', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should create a parse error when a string is returned', done => {
|
||||
const mock2 = mockAdapter;
|
||||
mock2.validateFilename = () => {
|
||||
return 'Bad file! No biscuit!';
|
||||
};
|
||||
const filesController = new FilesController(mockAdapter);
|
||||
const error = filesController.validateFilename();
|
||||
expect(typeof error).toBe('object');
|
||||
expect(error.message.indexOf('biscuit')).toBe(13);
|
||||
expect(error.code).toBe(Parse.Error.INVALID_FILE_NAME);
|
||||
mockAdapter.validateFilename = () => {
|
||||
return null;
|
||||
};
|
||||
done();
|
||||
});
|
||||
|
||||
it('should add a unique hash to the file name when the preserveFileName option is false', done => {
|
||||
const config = Config.get(Parse.applicationId);
|
||||
const gridFSAdapter = new GridFSBucketAdapter('mongodb://localhost:27017/parse');
|
||||
spyOn(gridFSAdapter, 'createFile');
|
||||
gridFSAdapter.createFile.and.returnValue(Promise.resolve());
|
||||
const gridStoreAdapter = new GridFSBucketAdapter(
|
||||
'mongodb://localhost:27017/parse'
|
||||
);
|
||||
spyOn(gridStoreAdapter, 'createFile');
|
||||
gridStoreAdapter.createFile.and.returnValue(Promise.resolve());
|
||||
const fileName = 'randomFileName.pdf';
|
||||
const regexEscapedFileName = fileName.replace(/\./g, '\\$&');
|
||||
const filesController = new FilesController(gridFSAdapter, null, {
|
||||
const filesController = new FilesController(gridStoreAdapter, null, {
|
||||
preserveFileName: false,
|
||||
});
|
||||
|
||||
filesController.createFile(config, fileName);
|
||||
|
||||
expect(gridFSAdapter.createFile).toHaveBeenCalledTimes(1);
|
||||
expect(gridFSAdapter.createFile.calls.mostRecent().args[0]).toMatch(
|
||||
expect(gridStoreAdapter.createFile).toHaveBeenCalledTimes(1);
|
||||
expect(gridStoreAdapter.createFile.calls.mostRecent().args[0]).toMatch(
|
||||
`^.{32}_${regexEscapedFileName}$`
|
||||
);
|
||||
|
||||
@@ -122,42 +99,23 @@ 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 gridFSAdapter = new GridFSBucketAdapter('mongodb://localhost:27017/parse');
|
||||
spyOn(gridFSAdapter, 'createFile');
|
||||
gridFSAdapter.createFile.and.returnValue(Promise.resolve());
|
||||
const gridStoreAdapter = new GridFSBucketAdapter(
|
||||
'mongodb://localhost:27017/parse'
|
||||
);
|
||||
spyOn(gridStoreAdapter, 'createFile');
|
||||
gridStoreAdapter.createFile.and.returnValue(Promise.resolve());
|
||||
const fileName = 'randomFileName.pdf';
|
||||
const filesController = new FilesController(gridFSAdapter, null, {
|
||||
const filesController = new FilesController(gridStoreAdapter, null, {
|
||||
preserveFileName: true,
|
||||
});
|
||||
|
||||
filesController.createFile(config, fileName);
|
||||
|
||||
expect(gridFSAdapter.createFile).toHaveBeenCalledTimes(1);
|
||||
expect(gridFSAdapter.createFile.calls.mostRecent().args[0]).toEqual(fileName);
|
||||
expect(gridStoreAdapter.createFile).toHaveBeenCalledTimes(1);
|
||||
expect(gridStoreAdapter.createFile.calls.mostRecent().args[0]).toEqual(
|
||||
fileName
|
||||
);
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
it('should handle adapter without getMetadata', async () => {
|
||||
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 gridFSAdapter = new GridFSBucketAdapter('mongodb://localhost:27017/parse');
|
||||
const fileName = 'foo/randomFileName.pdf';
|
||||
expect(gridFSAdapter.validateFilename(fileName)).not.toBe(null);
|
||||
done();
|
||||
});
|
||||
|
||||
it('should also reject slashes in file names', done => {
|
||||
const gridFSAdapter = new GridFSBucketAdapter('mongodb://localhost:27017/parse');
|
||||
const fileName = 'foo/randomFileName.pdf';
|
||||
expect(gridFSAdapter.validateFilename(fileName)).not.toBe(null);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
const GridStoreAdapter = require('../lib/Adapters/Files/GridStoreAdapter')
|
||||
.GridStoreAdapter;
|
||||
const GridFSBucketAdapter = require('../lib/Adapters/Files/GridFSBucketAdapter')
|
||||
.GridFSBucketAdapter;
|
||||
const { randomString } = require('../lib/cryptoUtils');
|
||||
const databaseURI = 'mongodb://localhost:27017/parse';
|
||||
const request = require('../lib/request');
|
||||
|
||||
async function expectMissingFile(gfsAdapter, name) {
|
||||
try {
|
||||
@@ -13,388 +14,23 @@ async function expectMissingFile(gfsAdapter, name) {
|
||||
}
|
||||
}
|
||||
|
||||
describe_only_db('mongo')('GridFSBucket', () => {
|
||||
describe('GridFSBucket and GridStore interop', () => {
|
||||
beforeEach(async () => {
|
||||
const gsAdapter = new GridFSBucketAdapter(databaseURI);
|
||||
const gsAdapter = new GridStoreAdapter(databaseURI);
|
||||
const db = await gsAdapter._connect();
|
||||
await db.dropDatabase();
|
||||
db.dropDatabase();
|
||||
});
|
||||
|
||||
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(
|
||||
databaseURI,
|
||||
{},
|
||||
'89E4AFF1-DFE4-4603-9574-BFA16BB446FD'
|
||||
);
|
||||
await expectMissingFile(encryptedAdapter, 'myFileName');
|
||||
const originalString = 'abcdefghi';
|
||||
await encryptedAdapter.createFile('myFileName', originalString);
|
||||
const unencryptedResult = await unencryptedAdapter.getFileData('myFileName');
|
||||
expect(unencryptedResult.toString('utf8')).not.toBe(originalString);
|
||||
const encryptedResult = await encryptedAdapter.getFileData('myFileName');
|
||||
expect(encryptedResult.toString('utf8')).toBe(originalString);
|
||||
});
|
||||
|
||||
it('should rotate key of all unencrypted GridFS files to encrypted files', async () => {
|
||||
const unencryptedAdapter = new GridFSBucketAdapter(databaseURI);
|
||||
const encryptedAdapter = new GridFSBucketAdapter(
|
||||
databaseURI,
|
||||
{},
|
||||
'89E4AFF1-DFE4-4603-9574-BFA16BB446FD'
|
||||
);
|
||||
const fileName1 = 'file1.txt';
|
||||
const data1 = 'hello world';
|
||||
const fileName2 = 'file2.txt';
|
||||
const data2 = 'hello new world';
|
||||
//Store unecrypted files
|
||||
await unencryptedAdapter.createFile(fileName1, data1);
|
||||
const unencryptedResult1 = await unencryptedAdapter.getFileData(fileName1);
|
||||
expect(unencryptedResult1.toString('utf8')).toBe(data1);
|
||||
await unencryptedAdapter.createFile(fileName2, data2);
|
||||
const unencryptedResult2 = await unencryptedAdapter.getFileData(fileName2);
|
||||
expect(unencryptedResult2.toString('utf8')).toBe(data2);
|
||||
//Check if encrypted adapter can read data and make sure it's not the same as unEncrypted adapter
|
||||
const { rotated, notRotated } = await encryptedAdapter.rotateEncryptionKey();
|
||||
expect(rotated.length).toEqual(2);
|
||||
expect(
|
||||
rotated.filter(function (value) {
|
||||
return value === fileName1;
|
||||
}).length
|
||||
).toEqual(1);
|
||||
expect(
|
||||
rotated.filter(function (value) {
|
||||
return value === fileName2;
|
||||
}).length
|
||||
).toEqual(1);
|
||||
expect(notRotated.length).toEqual(0);
|
||||
let result = await encryptedAdapter.getFileData(fileName1);
|
||||
expect(result instanceof Buffer).toBe(true);
|
||||
expect(result.toString('utf-8')).toEqual(data1);
|
||||
const encryptedData1 = await unencryptedAdapter.getFileData(fileName1);
|
||||
expect(encryptedData1.toString('utf-8')).not.toEqual(unencryptedResult1);
|
||||
result = await encryptedAdapter.getFileData(fileName2);
|
||||
expect(result instanceof Buffer).toBe(true);
|
||||
expect(result.toString('utf-8')).toEqual(data2);
|
||||
const encryptedData2 = await unencryptedAdapter.getFileData(fileName2);
|
||||
expect(encryptedData2.toString('utf-8')).not.toEqual(unencryptedResult2);
|
||||
});
|
||||
|
||||
it('should rotate key of all old encrypted GridFS files to encrypted files', async () => {
|
||||
const oldEncryptionKey = 'oldKeyThatILoved';
|
||||
const oldEncryptedAdapter = new GridFSBucketAdapter(databaseURI, {}, oldEncryptionKey);
|
||||
const encryptedAdapter = new GridFSBucketAdapter(databaseURI, {}, 'newKeyThatILove');
|
||||
const fileName1 = 'file1.txt';
|
||||
const data1 = 'hello world';
|
||||
const fileName2 = 'file2.txt';
|
||||
const data2 = 'hello new world';
|
||||
//Store unecrypted files
|
||||
await oldEncryptedAdapter.createFile(fileName1, data1);
|
||||
const oldEncryptedResult1 = await oldEncryptedAdapter.getFileData(fileName1);
|
||||
expect(oldEncryptedResult1.toString('utf8')).toBe(data1);
|
||||
await oldEncryptedAdapter.createFile(fileName2, data2);
|
||||
const oldEncryptedResult2 = await oldEncryptedAdapter.getFileData(fileName2);
|
||||
expect(oldEncryptedResult2.toString('utf8')).toBe(data2);
|
||||
//Check if encrypted adapter can read data and make sure it's not the same as unEncrypted adapter
|
||||
const { rotated, notRotated } = await encryptedAdapter.rotateEncryptionKey({
|
||||
oldKey: oldEncryptionKey,
|
||||
});
|
||||
expect(rotated.length).toEqual(2);
|
||||
expect(
|
||||
rotated.filter(function (value) {
|
||||
return value === fileName1;
|
||||
}).length
|
||||
).toEqual(1);
|
||||
expect(
|
||||
rotated.filter(function (value) {
|
||||
return value === fileName2;
|
||||
}).length
|
||||
).toEqual(1);
|
||||
expect(notRotated.length).toEqual(0);
|
||||
let result = await encryptedAdapter.getFileData(fileName1);
|
||||
expect(result instanceof Buffer).toBe(true);
|
||||
expect(result.toString('utf-8')).toEqual(data1);
|
||||
let decryptionError1;
|
||||
let encryptedData1;
|
||||
try {
|
||||
encryptedData1 = await oldEncryptedAdapter.getFileData(fileName1);
|
||||
} catch (err) {
|
||||
decryptionError1 = err;
|
||||
}
|
||||
expect(decryptionError1).toMatch('Error');
|
||||
expect(encryptedData1).toBeUndefined();
|
||||
result = await encryptedAdapter.getFileData(fileName2);
|
||||
expect(result instanceof Buffer).toBe(true);
|
||||
expect(result.toString('utf-8')).toEqual(data2);
|
||||
let decryptionError2;
|
||||
let encryptedData2;
|
||||
try {
|
||||
encryptedData2 = await oldEncryptedAdapter.getFileData(fileName2);
|
||||
} catch (err) {
|
||||
decryptionError2 = err;
|
||||
}
|
||||
expect(decryptionError2).toMatch('Error');
|
||||
expect(encryptedData2).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should rotate key of all old encrypted GridFS files to unencrypted files', async () => {
|
||||
const oldEncryptionKey = 'oldKeyThatILoved';
|
||||
const oldEncryptedAdapter = new GridFSBucketAdapter(databaseURI, {}, oldEncryptionKey);
|
||||
const unEncryptedAdapter = new GridFSBucketAdapter(databaseURI);
|
||||
const fileName1 = 'file1.txt';
|
||||
const data1 = 'hello world';
|
||||
const fileName2 = 'file2.txt';
|
||||
const data2 = 'hello new world';
|
||||
//Store unecrypted files
|
||||
await oldEncryptedAdapter.createFile(fileName1, data1);
|
||||
const oldEncryptedResult1 = await oldEncryptedAdapter.getFileData(fileName1);
|
||||
expect(oldEncryptedResult1.toString('utf8')).toBe(data1);
|
||||
await oldEncryptedAdapter.createFile(fileName2, data2);
|
||||
const oldEncryptedResult2 = await oldEncryptedAdapter.getFileData(fileName2);
|
||||
expect(oldEncryptedResult2.toString('utf8')).toBe(data2);
|
||||
//Check if unEncrypted adapter can read data and make sure it's not the same as oldEncrypted adapter
|
||||
const { rotated, notRotated } = await unEncryptedAdapter.rotateEncryptionKey({
|
||||
oldKey: oldEncryptionKey,
|
||||
});
|
||||
expect(rotated.length).toEqual(2);
|
||||
expect(
|
||||
rotated.filter(function (value) {
|
||||
return value === fileName1;
|
||||
}).length
|
||||
).toEqual(1);
|
||||
expect(
|
||||
rotated.filter(function (value) {
|
||||
return value === fileName2;
|
||||
}).length
|
||||
).toEqual(1);
|
||||
expect(notRotated.length).toEqual(0);
|
||||
let result = await unEncryptedAdapter.getFileData(fileName1);
|
||||
expect(result instanceof Buffer).toBe(true);
|
||||
expect(result.toString('utf-8')).toEqual(data1);
|
||||
let decryptionError1;
|
||||
let encryptedData1;
|
||||
try {
|
||||
encryptedData1 = await oldEncryptedAdapter.getFileData(fileName1);
|
||||
} catch (err) {
|
||||
decryptionError1 = err;
|
||||
}
|
||||
expect(decryptionError1).toMatch('Error');
|
||||
expect(encryptedData1).toBeUndefined();
|
||||
result = await unEncryptedAdapter.getFileData(fileName2);
|
||||
expect(result instanceof Buffer).toBe(true);
|
||||
expect(result.toString('utf-8')).toEqual(data2);
|
||||
let decryptionError2;
|
||||
let encryptedData2;
|
||||
try {
|
||||
encryptedData2 = await oldEncryptedAdapter.getFileData(fileName2);
|
||||
} catch (err) {
|
||||
decryptionError2 = err;
|
||||
}
|
||||
expect(decryptionError2).toMatch('Error');
|
||||
expect(encryptedData2).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should only encrypt specified fileNames', async () => {
|
||||
const oldEncryptionKey = 'oldKeyThatILoved';
|
||||
const oldEncryptedAdapter = new GridFSBucketAdapter(databaseURI, {}, oldEncryptionKey);
|
||||
const encryptedAdapter = new GridFSBucketAdapter(databaseURI, {}, 'newKeyThatILove');
|
||||
const unEncryptedAdapter = new GridFSBucketAdapter(databaseURI);
|
||||
const fileName1 = 'file1.txt';
|
||||
const data1 = 'hello world';
|
||||
const fileName2 = 'file2.txt';
|
||||
const data2 = 'hello new world';
|
||||
//Store unecrypted files
|
||||
await oldEncryptedAdapter.createFile(fileName1, data1);
|
||||
const oldEncryptedResult1 = await oldEncryptedAdapter.getFileData(fileName1);
|
||||
expect(oldEncryptedResult1.toString('utf8')).toBe(data1);
|
||||
await oldEncryptedAdapter.createFile(fileName2, data2);
|
||||
const oldEncryptedResult2 = await oldEncryptedAdapter.getFileData(fileName2);
|
||||
expect(oldEncryptedResult2.toString('utf8')).toBe(data2);
|
||||
//Inject unecrypted file to see if causes an issue
|
||||
const fileName3 = 'file3.txt';
|
||||
const data3 = 'hello past world';
|
||||
await unEncryptedAdapter.createFile(fileName3, data3, 'text/utf8');
|
||||
//Check if encrypted adapter can read data and make sure it's not the same as unEncrypted adapter
|
||||
const { rotated, notRotated } = await encryptedAdapter.rotateEncryptionKey({
|
||||
oldKey: oldEncryptionKey,
|
||||
fileNames: [fileName1, fileName2],
|
||||
});
|
||||
expect(rotated.length).toEqual(2);
|
||||
expect(
|
||||
rotated.filter(function (value) {
|
||||
return value === fileName1;
|
||||
}).length
|
||||
).toEqual(1);
|
||||
expect(
|
||||
rotated.filter(function (value) {
|
||||
return value === fileName2;
|
||||
}).length
|
||||
).toEqual(1);
|
||||
expect(notRotated.length).toEqual(0);
|
||||
expect(
|
||||
rotated.filter(function (value) {
|
||||
return value === fileName3;
|
||||
}).length
|
||||
).toEqual(0);
|
||||
let result = await encryptedAdapter.getFileData(fileName1);
|
||||
expect(result instanceof Buffer).toBe(true);
|
||||
expect(result.toString('utf-8')).toEqual(data1);
|
||||
let decryptionError1;
|
||||
let encryptedData1;
|
||||
try {
|
||||
encryptedData1 = await oldEncryptedAdapter.getFileData(fileName1);
|
||||
} catch (err) {
|
||||
decryptionError1 = err;
|
||||
}
|
||||
expect(decryptionError1).toMatch('Error');
|
||||
expect(encryptedData1).toBeUndefined();
|
||||
result = await encryptedAdapter.getFileData(fileName2);
|
||||
expect(result instanceof Buffer).toBe(true);
|
||||
expect(result.toString('utf-8')).toEqual(data2);
|
||||
let decryptionError2;
|
||||
let encryptedData2;
|
||||
try {
|
||||
encryptedData2 = await oldEncryptedAdapter.getFileData(fileName2);
|
||||
} catch (err) {
|
||||
decryptionError2 = err;
|
||||
}
|
||||
expect(decryptionError2).toMatch('Error');
|
||||
expect(encryptedData2).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should return fileNames of those it can't encrypt with the new key", async () => {
|
||||
const oldEncryptionKey = 'oldKeyThatILoved';
|
||||
const oldEncryptedAdapter = new GridFSBucketAdapter(databaseURI, {}, oldEncryptionKey);
|
||||
const encryptedAdapter = new GridFSBucketAdapter(databaseURI, {}, 'newKeyThatILove');
|
||||
const unEncryptedAdapter = new GridFSBucketAdapter(databaseURI);
|
||||
const fileName1 = 'file1.txt';
|
||||
const data1 = 'hello world';
|
||||
const fileName2 = 'file2.txt';
|
||||
const data2 = 'hello new world';
|
||||
//Store unecrypted files
|
||||
await oldEncryptedAdapter.createFile(fileName1, data1);
|
||||
const oldEncryptedResult1 = await oldEncryptedAdapter.getFileData(fileName1);
|
||||
expect(oldEncryptedResult1.toString('utf8')).toBe(data1);
|
||||
await oldEncryptedAdapter.createFile(fileName2, data2);
|
||||
const oldEncryptedResult2 = await oldEncryptedAdapter.getFileData(fileName2);
|
||||
expect(oldEncryptedResult2.toString('utf8')).toBe(data2);
|
||||
//Inject unecrypted file to see if causes an issue
|
||||
const fileName3 = 'file3.txt';
|
||||
const data3 = 'hello past world';
|
||||
await unEncryptedAdapter.createFile(fileName3, data3, 'text/utf8');
|
||||
//Check if encrypted adapter can read data and make sure it's not the same as unEncrypted adapter
|
||||
const { rotated, notRotated } = await encryptedAdapter.rotateEncryptionKey({
|
||||
oldKey: oldEncryptionKey,
|
||||
});
|
||||
expect(rotated.length).toEqual(2);
|
||||
expect(
|
||||
rotated.filter(function (value) {
|
||||
return value === fileName1;
|
||||
}).length
|
||||
).toEqual(1);
|
||||
expect(
|
||||
rotated.filter(function (value) {
|
||||
return value === fileName2;
|
||||
}).length
|
||||
).toEqual(1);
|
||||
expect(notRotated.length).toEqual(1);
|
||||
expect(
|
||||
notRotated.filter(function (value) {
|
||||
return value === fileName3;
|
||||
}).length
|
||||
).toEqual(1);
|
||||
let result = await encryptedAdapter.getFileData(fileName1);
|
||||
expect(result instanceof Buffer).toBe(true);
|
||||
expect(result.toString('utf-8')).toEqual(data1);
|
||||
let decryptionError1;
|
||||
let encryptedData1;
|
||||
try {
|
||||
encryptedData1 = await oldEncryptedAdapter.getFileData(fileName1);
|
||||
} catch (err) {
|
||||
decryptionError1 = err;
|
||||
}
|
||||
expect(decryptionError1).toMatch('Error');
|
||||
expect(encryptedData1).toBeUndefined();
|
||||
result = await encryptedAdapter.getFileData(fileName2);
|
||||
expect(result instanceof Buffer).toBe(true);
|
||||
expect(result.toString('utf-8')).toEqual(data2);
|
||||
let decryptionError2;
|
||||
let encryptedData2;
|
||||
try {
|
||||
encryptedData2 = await oldEncryptedAdapter.getFileData(fileName2);
|
||||
} catch (err) {
|
||||
decryptionError2 = err;
|
||||
}
|
||||
expect(decryptionError2).toMatch('Error');
|
||||
expect(encryptedData2).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should save metadata', async () => {
|
||||
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';
|
||||
const metadata = { hello: 'world' };
|
||||
await gfsAdapter.createFile('myFileName', originalString, null, {
|
||||
metadata,
|
||||
});
|
||||
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);
|
||||
let gfsMetadata = await gfsAdapter.getMetadata('myFileName');
|
||||
expect(gfsMetadata.metadata).toEqual(metadata);
|
||||
|
||||
// Empty json for file not found
|
||||
gfsMetadata = await gfsAdapter.getMetadata('myUnknownFile');
|
||||
expect(gfsMetadata).toEqual({});
|
||||
});
|
||||
|
||||
it('should save metadata with file', async () => {
|
||||
const gfsAdapter = new GridFSBucketAdapter(databaseURI);
|
||||
await reconfigureServer({ filesAdapter: gfsAdapter });
|
||||
const str = 'Hello World!';
|
||||
const data = [];
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
data.push(str.charCodeAt(i));
|
||||
}
|
||||
const metadata = { foo: 'bar' };
|
||||
const file = new Parse.File('hello.txt', data, 'text/plain');
|
||||
file.addMetadata('foo', 'bar');
|
||||
await file.save();
|
||||
let fileData = await gfsAdapter.getMetadata(file.name());
|
||||
expect(fileData.metadata).toEqual(metadata);
|
||||
|
||||
// Can only add metadata on create
|
||||
file.addMetadata('hello', 'world');
|
||||
await file.save();
|
||||
fileData = await gfsAdapter.getMetadata(file.name());
|
||||
expect(fileData.metadata).toEqual(metadata);
|
||||
|
||||
const headers = {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest',
|
||||
};
|
||||
const response = await request({
|
||||
method: 'GET',
|
||||
headers,
|
||||
url: `http://localhost:8378/1/files/test/metadata/${file.name()}`,
|
||||
});
|
||||
fileData = response.data;
|
||||
expect(fileData.metadata).toEqual(metadata);
|
||||
});
|
||||
|
||||
it('should handle getMetadata error', async () => {
|
||||
const gfsAdapter = new GridFSBucketAdapter(databaseURI);
|
||||
await reconfigureServer({ filesAdapter: gfsAdapter });
|
||||
gfsAdapter.getMetadata = () => Promise.reject();
|
||||
|
||||
const headers = {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest',
|
||||
};
|
||||
const response = await request({
|
||||
method: 'GET',
|
||||
headers,
|
||||
url: `http://localhost:8378/1/files/test/metadata/filename.txt`,
|
||||
});
|
||||
expect(response.data).toEqual({});
|
||||
});
|
||||
|
||||
it('properly fetches a large file from GridFS', async () => {
|
||||
@@ -424,21 +60,4 @@ describe_only_db('mongo')('GridFSBucket', () => {
|
||||
await gfsAdapter.deleteFile('myFileName');
|
||||
await expectMissingFile(gfsAdapter, 'myFileName');
|
||||
});
|
||||
|
||||
it('handleShutdown, close connection', async () => {
|
||||
const databaseURI = 'mongodb://localhost:27017/parse';
|
||||
const gfsAdapter = new GridFSBucketAdapter(databaseURI);
|
||||
|
||||
const db = await gfsAdapter._connect();
|
||||
const status = await db.admin().serverStatus();
|
||||
expect(status.connections.current > 0).toEqual(true);
|
||||
|
||||
await gfsAdapter.handleShutdown();
|
||||
try {
|
||||
await db.admin().serverStatus();
|
||||
expect(false).toBe(true);
|
||||
} catch (e) {
|
||||
expect(e.message).toEqual('MongoClient must be connected to perform this operation');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
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();
|
||||
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);
|
||||
});
|
||||
});
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user