diff --git a/.github/workflows/codeql-phpstan.yml b/.github/workflows/codeql-phpstan.yml
deleted file mode 100644
index 3253e2c38b..0000000000
--- a/.github/workflows/codeql-phpstan.yml
+++ /dev/null
@@ -1,16 +0,0 @@
-name: "CodeQL"
-
-on: [pull_request]
-jobs:
- lint:
- name: CodeQL
- runs-on: ubuntu-latest
-
- steps:
- - name: Check out the repo
- uses: actions/checkout@v2
-
- - name: Run CodeQL
- run: |
- docker run --rm -v $PWD:/app composer sh -c \
- "composer install --profile --ignore-platform-reqs && composer check"
\ No newline at end of file
diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml
new file mode 100644
index 0000000000..80d880244c
--- /dev/null
+++ b/.github/workflows/nightly.yml
@@ -0,0 +1,47 @@
+name: Nightly Security Scan
+on:
+ schedule:
+ - cron: '0 0 * * *' # 12am UTC daily runtime
+ workflow_dispatch:
+
+jobs:
+ scan-image:
+ name: Scan Docker Image
+ runs-on: ubuntu-latest
+ steps:
+ - name: Check out code
+ uses: actions/checkout@v4
+ with:
+ submodules: recursive
+ - name: Build the Docker image
+ run: docker build . -t appwrite_image:latest
+ - name: Run Trivy vulnerability scanner on image
+ uses: aquasecurity/trivy-action@0.20.0
+ with:
+ image-ref: 'appwrite_image:latest'
+ format: 'sarif'
+ output: 'trivy-image-results.sarif'
+ ignore-unfixed: 'false'
+ severity: 'CRITICAL,HIGH'
+ - name: Upload Docker Image Scan Results
+ uses: github/codeql-action/upload-sarif@v2
+ with:
+ sarif_file: 'trivy-image-results.sarif'
+
+ scan-code:
+ name: Scan Code
+ runs-on: ubuntu-latest
+ steps:
+ - name: Check out code
+ uses: actions/checkout@v4
+ - name: Run Trivy vulnerability scanner on filesystem
+ uses: aquasecurity/trivy-action@0.20.0
+ with:
+ scan-type: 'fs'
+ format: 'sarif'
+ output: 'trivy-fs-results.sarif'
+ severity: 'CRITICAL,HIGH'
+ - name: Upload Code Scan Results
+ uses: github/codeql-action/upload-sarif@v2
+ with:
+ sarif_file: 'trivy-fs-results.sarif'
diff --git a/.github/workflows/pr-scan.yml b/.github/workflows/pr-scan.yml
index af510ccc3b..eded58985d 100644
--- a/.github/workflows/pr-scan.yml
+++ b/.github/workflows/pr-scan.yml
@@ -1,17 +1,22 @@
name: PR Security Scan
-on:
- pull_request:
+on:
+ pull_request_target:
types: [opened, synchronize, reopened]
- workflow_dispatch:
+
jobs:
scan:
runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ pull-requests: write
steps:
- - name: Check out code
+ - name: Check out code
uses: actions/checkout@v4
with:
+ ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 0
submodules: 'recursive'
+
- name: Build the Docker image
uses: docker/build-push-action@v5
with:
@@ -19,6 +24,7 @@ jobs:
push: false
load: true
tags: pr_image:${{ github.sha }}
+
- name: Run Trivy vulnerability scanner on image
uses: aquasecurity/trivy-action@0.20.0
with:
@@ -26,6 +32,7 @@ jobs:
format: 'json'
output: 'trivy-image-results.json'
severity: 'CRITICAL,HIGH'
+
- name: Run Trivy vulnerability scanner on source code
uses: aquasecurity/trivy-action@0.20.0
with:
@@ -34,10 +41,11 @@ jobs:
format: 'json'
output: 'trivy-fs-results.json'
severity: 'CRITICAL,HIGH'
- - name: Process and post Trivy scan results
+
+ - name: Process Trivy scan results
+ id: process-results
uses: actions/github-script@v7
with:
- github-token: ${{secrets.GITHUB_TOKEN}}
script: |
const fs = require('fs');
let commentBody = '## Security Scan Results for PR\n\n';
@@ -79,9 +87,19 @@ jobs:
commentBody += 'Please contact the core team for assistance.';
}
- github.rest.issues.createComment({
- issue_number: context.issue.number,
- owner: context.repo.owner,
- repo: context.repo.repo,
- body: commentBody
- });
+ core.setOutput('comment-body', commentBody);
+ - name: Find Comment
+ uses: peter-evans/find-comment@v3
+ id: fc
+ with:
+ issue-number: ${{ github.event.pull_request.number }}
+ comment-author: 'github-actions[bot]'
+ body-includes: Security Scan Results for PR
+
+ - name: Create or update comment
+ uses: peter-evans/create-or-update-comment@v3
+ with:
+ issue-number: ${{ github.event.pull_request.number }}
+ comment-id: ${{ steps.fc.outputs.comment-id }}
+ body: ${{ steps.process-results.outputs.comment-body }}
+ edit-mode: replace
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 633bd46ea4..6e7012527d 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -16,22 +16,22 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
with:
submodules: recursive
- name: Set up Docker Buildx
- uses: docker/setup-buildx-action@v2
+ uses: docker/setup-buildx-action@v3
- name: Build Appwrite
- uses: docker/build-push-action@v3
+ uses: docker/build-push-action@v6
with:
context: .
push: false
tags: ${{ env.IMAGE }}
load: true
- cache-from: type=gha
- cache-to: type=gha,mode=max
+ cache-from: type=gha,scope=appwrite
+ cache-to: type=gha,mode=max,scope=appwrite
outputs: type=docker,dest=/tmp/${{ env.IMAGE }}.tar
build-args: |
DEBUG=false
@@ -39,9 +39,11 @@ jobs:
VERSION=dev
- name: Cache Docker Image
- uses: actions/cache@v3
+ uses: actions/cache@v4
with:
key: ${{ env.CACHE_KEY }}
+ restore-keys: |
+ appwrite-dev-
path: /tmp/${{ env.IMAGE }}.tar
unit_test:
@@ -51,10 +53,10 @@ jobs:
steps:
- name: checkout
- uses: actions/checkout@v2
+ uses: actions/checkout@v4
- name: Load Cache
- uses: actions/cache@v3
+ uses: actions/cache@v4
with:
key: ${{ env.CACHE_KEY }}
path: /tmp/${{ env.IMAGE }}.tar
@@ -81,10 +83,10 @@ jobs:
needs: setup
steps:
- name: checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: Load Cache
- uses: actions/cache@v3
+ uses: actions/cache@v4
with:
key: ${{ env.CACHE_KEY }}
path: /tmp/${{ env.IMAGE }}.tar
@@ -113,6 +115,7 @@ jobs:
Console,
Databases,
Functions,
+ FunctionsSchedule,
GraphQL,
Health,
Locale,
@@ -128,10 +131,10 @@ jobs:
steps:
- name: checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: Load Cache
- uses: actions/cache@v3
+ uses: actions/cache@v4
with:
key: ${{ env.CACHE_KEY }}
path: /tmp/${{ env.IMAGE }}.tar
@@ -141,7 +144,7 @@ jobs:
run: |
docker load --input /tmp/${{ env.IMAGE }}.tar
docker compose up -d
- sleep 25
+ sleep 30
- name: Run ${{matrix.service}} Tests
run: docker compose exec -T appwrite test /usr/src/code/tests/e2e/Services/${{matrix.service}} --debug
@@ -149,15 +152,15 @@ jobs:
- name: Run ${{matrix.service}} Shared Tables Tests
run: _APP_DATABASE_SHARED_TABLES=database_db_main docker compose exec -T appwrite test /usr/src/code/tests/e2e/Services/${{matrix.service}} --debug
- benchamrking:
+ benchmarking:
name: Benchmark
runs-on: ubuntu-latest
needs: setup
steps:
- name: Checkout repository
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: Load Cache
- uses: actions/cache@v3
+ uses: actions/cache@v4
with:
key: ${{ env.CACHE_KEY }}
path: /tmp/${{ env.IMAGE }}.tar
diff --git a/CHANGES.md b/CHANGES.md
index e6649d795e..9b6172eeab 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,3 +1,145 @@
+# Version 1.6.0
+
+## What's Changed
+
+### Notable changes
+
+* Allow execution filter attributes in [#7607](https://github.com/appwrite/appwrite/pull/7607)
+* Add dynamic API keys for function executions in [#7512](https://github.com/appwrite/appwrite/pull/7512)
+* Add metrics for successful and failed builds in [#8210](https://github.com/appwrite/appwrite/pull/8210)
+* Update logging config to use a DSN approach in [#8187](https://github.com/appwrite/appwrite/pull/8187)
+* Add projects.createJWT endpoint for dynamic keys in [#8213](https://github.com/appwrite/appwrite/pull/8213)
+* Add users.createJWT() endpoint for local function development in [#8207](https://github.com/appwrite/appwrite/pull/8207)
+* Added cancel build endpoint in [#7605](https://github.com/appwrite/appwrite/pull/7605)
+* Add CLI as a function deployment type in [#8215](https://github.com/appwrite/appwrite/pull/8215)
+* Add vcs.getRepositoryContents() endpoint in [#8330](https://github.com/appwrite/appwrite/pull/8330)
+* Add appwrite version in function variables in [#8336](https://github.com/appwrite/appwrite/pull/8336)
+* Add support for scheduled executions in [#8243](https://github.com/appwrite/appwrite/pull/8243)
+* Add endpoint to delete execution in [#8337](https://github.com/appwrite/appwrite/pull/8337)
+* OPR v4 support in [#8323](https://github.com/appwrite/appwrite/pull/8323)
+* Mock OTP and phone numbers in [#7565](https://github.com/appwrite/appwrite/pull/7565)
+* Support scheduled executions in [#8355](https://github.com/appwrite/appwrite/pull/8355)
+* Add alert for new sessions in [#8315](https://github.com/appwrite/appwrite/pull/8315)
+* Update delete authenticator to remove OTP Validation in [#8367](https://github.com/appwrite/appwrite/pull/8367)
+* Track project last activity in [#8366](https://github.com/appwrite/appwrite/pull/8366)
+* Containerize the console in [#8406](https://github.com/appwrite/appwrite/pull/8406)
+* Implement MBSeconds Metric on 1.5.X in [#8385](https://github.com/appwrite/appwrite/pull/8385)
+* Support JWTs without session ID in [#8420](https://github.com/appwrite/appwrite/pull/8420)
+* 1.6.x sdks in [#8359](https://github.com/appwrite/appwrite/pull/8359)
+* Base migration for 1.6.x in [#8417](https://github.com/appwrite/appwrite/pull/8417)
+* 1.6.x migrations and filters in [#8403](https://github.com/appwrite/appwrite/pull/8403)
+* Add APPWRITE_REGION in function variables in [#8394](https://github.com/appwrite/appwrite/pull/8394)
+* Support dynamic keys for domain executions in [#8428](https://github.com/appwrite/appwrite/pull/8428)
+* Bump DBIP to latest version in [#8467](https://github.com/appwrite/appwrite/pull/8467)
+* Automatically restart function on crash in [#8473](https://github.com/appwrite/appwrite/pull/8473)
+* Don't send session alerts for otp and magic-url logins in [#8459](https://github.com/appwrite/appwrite/pull/8459)
+* Mark 4XX executions as successful in [#8493](https://github.com/appwrite/appwrite/pull/8493)
+* Add dynamic keys in builds in [#8492](https://github.com/appwrite/appwrite/pull/8492)
+* Allow deployment queries on type and size in [#8515](https://github.com/appwrite/appwrite/pull/8515)
+* Add OTP email template in [#8501](https://github.com/appwrite/appwrite/pull/8501)
+* Update console links in [#8523](https://github.com/appwrite/appwrite/pull/8523)
+* Add multipart support in [#8477](https://github.com/appwrite/appwrite/pull/8477)
+* Separate deployment sizes in [#8556](https://github.com/appwrite/appwrite/pull/8556)
+* Add go runtime in [#8572](https://github.com/appwrite/appwrite/pull/8572)
+* Add react native platform in [#8562](https://github.com/appwrite/appwrite/pull/8562)
+* Merge deployments and build storage metrics together in API in [#8443](https://github.com/appwrite/appwrite/pull/8443)
+* Support string attribute resizing in [#8597](https://github.com/appwrite/appwrite/pull/8597)
+* Support renaming attributes in [#8544](https://github.com/appwrite/appwrite/pull/8544)
+* Add VCS vars to deployments & executions in [#8631](https://github.com/appwrite/appwrite/pull/8631)
+* Function storage metrics in [#8668](https://github.com/appwrite/appwrite/pull/8668)
+* External messaging usage count in [#8672](https://github.com/appwrite/appwrite/pull/8672)
+
+### Fixes
+
+* Fix execution duration in [#8357](https://github.com/appwrite/appwrite/pull/8357)
+* Fix file size calculations in [#8432](https://github.com/appwrite/appwrite/pull/8432)
+* Fix disabled function logging in [#8398](https://github.com/appwrite/appwrite/pull/8398)
+* Fix function redeployments in [#8434](https://github.com/appwrite/appwrite/pull/8434)
+* Add value to variables template in [#8483](https://github.com/appwrite/appwrite/pull/8483)
+* Fix build size limits in [#8396](https://github.com/appwrite/appwrite/pull/8396)
+* Fix deployment method name in [#8490](https://github.com/appwrite/appwrite/pull/8490)
+* Fix function disconnecting from git in [#8500](https://github.com/appwrite/appwrite/pull/8500)
+* Increase buckets metadata in [#8452](https://github.com/appwrite/appwrite/pull/8452)
+* Fix deploy from git with space in [#8517](https://github.com/appwrite/appwrite/pull/8517)
+* Fix missing build logs in [#8484](https://github.com/appwrite/appwrite/pull/8484)
+* Delete team memberships synchronously in [#8217](https://github.com/appwrite/appwrite/pull/8217)
+* Fix Anyof validator in specs in [#8543](https://github.com/appwrite/appwrite/pull/8543)
+* Fix missing function variables in [#8554](https://github.com/appwrite/appwrite/pull/8554)
+* Fix deadlock in [#8609](https://github.com/appwrite/appwrite/pull/8609)
+* Fix domain execution stats in [#8608](https://github.com/appwrite/appwrite/pull/8608)
+* Update console redirect to include query params in [#8619](https://github.com/appwrite/appwrite/pull/8619)
+* Update abuse-key for mfa challenge endpoints in [#8649](https://github.com/appwrite/appwrite/pull/8649)
+* Fix cross-project scheduler stability in [#8641](https://github.com/appwrite/appwrite/pull/8641)
+* Fix vcs deployment size in [#8640](https://github.com/appwrite/appwrite/pull/8640)
+* Fix logging behaviour for Functions in [#8627](https://github.com/appwrite/appwrite/pull/8627)
+* Add retention env vars to deletes worker in [#8662](https://github.com/appwrite/appwrite/pull/8662)
+* Fix scheduled executions data in [#8639](https://github.com/appwrite/appwrite/pull/8639)
+
+### Miscellaneous
+
+* Sync 1.6.x with main in [#8163](https://github.com/appwrite/appwrite/pull/8163)
+* Remove build ID from rebuild deployment endpoint in [#8214](https://github.com/appwrite/appwrite/pull/8214)
+* 1.6.x specs in [#8304](https://github.com/appwrite/appwrite/pull/8304)
+* Sync with main in [#8295](https://github.com/appwrite/appwrite/pull/8295)
+* Fix 1.6.x failing tests in [#8333](https://github.com/appwrite/appwrite/pull/8333)
+* Ensure CI/CD works in [#8350](https://github.com/appwrite/appwrite/pull/8350)
+* Update specs in [#8356](https://github.com/appwrite/appwrite/pull/8356)
+* Sync main to 1.6.x in [#8430](https://github.com/appwrite/appwrite/pull/8430)
+* Add scheduledAt in execution response model in [#8425](https://github.com/appwrite/appwrite/pull/8425)
+* Move functions marketplace to appwrite in [#8427](https://github.com/appwrite/appwrite/pull/8427)
+* Refactor deployment check in function tests in [#8444](https://github.com/appwrite/appwrite/pull/8444)
+* Add ci/cd benchmark in [#8414](https://github.com/appwrite/appwrite/pull/8414)
+* Upgrade SDK version in [#8465](https://github.com/appwrite/appwrite/pull/8465)
+* Improve session alert in [#8399](https://github.com/appwrite/appwrite/pull/8399)
+* Address review comments in [#8422](https://github.com/appwrite/appwrite/pull/8422)
+* Add scopes to function template in [#8496](https://github.com/appwrite/appwrite/pull/8496)
+* Update benchmark comment in [#8507](https://github.com/appwrite/appwrite/pull/8507)
+* Add key to runtime model in [#8503](https://github.com/appwrite/appwrite/pull/8503)
+* Upgrade logger in [#8497](https://github.com/appwrite/appwrite/pull/8497)
+* Change default email addresses in [#8466](https://github.com/appwrite/appwrite/pull/8466)
+* Improve scheduled executions in [#8412](https://github.com/appwrite/appwrite/pull/8412)
+* Sync 1.5.x into main in [#8509](https://github.com/appwrite/appwrite/pull/8509)
+* Sync 1.6 with main in [#8529](https://github.com/appwrite/appwrite/pull/8529)
+* Fix templates CORS in [#8528](https://github.com/appwrite/appwrite/pull/8528)
+* Update size to specification for variable runtimes in [#8537](https://github.com/appwrite/appwrite/pull/8537)
+* Add boundary to multipart header in [#8539](https://github.com/appwrite/appwrite/pull/8539)
+* Support manual templates in [#8527](https://github.com/appwrite/appwrite/pull/8527)
+* Reorder runtimes in [#8540](https://github.com/appwrite/appwrite/pull/8540)
+* Fix 1.6 bugs in [#8358](https://github.com/appwrite/appwrite/pull/8358)
+* Add seconds precision to scheduledAt in [#8546](https://github.com/appwrite/appwrite/pull/8546)
+* Update docker base image in [#8485](https://github.com/appwrite/appwrite/pull/8485)
+* Update create execution return type in [#8542](https://github.com/appwrite/appwrite/pull/8542)
+* Default fallback to for templateBranch in [#8547](https://github.com/appwrite/appwrite/pull/8547)
+* Fix env vars functions test in [#8555](https://github.com/appwrite/appwrite/pull/8555)
+* Fix session alerts in [#8550](https://github.com/appwrite/appwrite/pull/8550)
+* Add runtime controls in [#8384](https://github.com/appwrite/appwrite/pull/8384)
+* Revert request type to json in create execution in [#8563](https://github.com/appwrite/appwrite/pull/8563)
+* Sync 1.6.x Filters and Migrations with latest in [#8553](https://github.com/appwrite/appwrite/pull/8553)
+* Update sdks in [#8551](https://github.com/appwrite/appwrite/pull/8551)
+* Update Docs in [#8567](https://github.com/appwrite/appwrite/pull/8567)
+* Headers validator benchmark in [#8561](https://github.com/appwrite/appwrite/pull/8561)
+* Fix go version in [#8571](https://github.com/appwrite/appwrite/pull/8571)
+* Update dependencies in [#8574](https://github.com/appwrite/appwrite/pull/8574)
+* Upgrade console in [#8575](https://github.com/appwrite/appwrite/pull/8575)
+* 1.6.x logging test in [#8580](https://github.com/appwrite/appwrite/pull/8580)
+* Bump console sdk in [#8581](https://github.com/appwrite/appwrite/pull/8581)
+* Update sdks in [#8582](https://github.com/appwrite/appwrite/pull/8582)
+* Add changelogs for dart and flutter in [#8587](https://github.com/appwrite/appwrite/pull/8587)
+* Add payload validator in [#8594](https://github.com/appwrite/appwrite/pull/8594)
+* Update geodb in [#8615](https://github.com/appwrite/appwrite/pull/8615)
+* Update createdeployment methodtype to upload in [#8616](https://github.com/appwrite/appwrite/pull/8616)
+* Remove tenant in document filter in [#8624](https://github.com/appwrite/appwrite/pull/8624)
+* Improve mail datetime format in [#8628](https://github.com/appwrite/appwrite/pull/8628)
+* Fix router function execution logging in [#8625](https://github.com/appwrite/appwrite/pull/8625)
+* Add Functions templates async test in [#8622](https://github.com/appwrite/appwrite/pull/8622)
+* Update console in [#8629](https://github.com/appwrite/appwrite/pull/8629)
+* 1.6.1 in [#8630](https://github.com/appwrite/appwrite/pull/8630)
+* Update version in [#8646](https://github.com/appwrite/appwrite/pull/8646)
+* Phone auth metric rename in [#8648](https://github.com/appwrite/appwrite/pull/8648)
+* Pretty print specs in [#8643](https://github.com/appwrite/appwrite/pull/8643)
+* Fix messaging metrics in [#8674](https://github.com/appwrite/appwrite/pull/8674)
+* Bump console to 5.0.6 in [#8585](https://github.com/appwrite/appwrite/pull/8585)
+
# Version 1.5.10
## What's Changed
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index d3a7d060b0..b92361e51a 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -319,10 +319,13 @@ These are the current metrics we collect usage stats for:
| users | Total number of users per project|
| executions | Total number of executions per project |
| databases | Total number of databases per project |
+| databases.storage | Total amount of storage used by all databases per project (in bytes) |
| collections | Total number of collections per project |
| {databaseInternalId}.collections | Total number of collections per database|
+| {databaseInternalId}.storage | Sum of database storage (in bytes) |
| documents | Total number of documents per project |
| {databaseInternalId}.{collectionInternalId}.documents | Total number of documents per collection |
+| {databaseInternalId}.{collectionInternalId}.storage | Sum of database storage used by the collection (in bytes) |
| buckets | Total number of buckets per project |
| files | Total number of files per project |
| {bucketInternalId}.files.storage | Sum of files.storage per bucket (in bytes) |
diff --git a/Dockerfile b/Dockerfile
index dc334e53d0..13b018df0f 100755
--- a/Dockerfile
+++ b/Dockerfile
@@ -12,7 +12,7 @@ RUN composer install --ignore-platform-reqs --optimize-autoloader \
--no-plugins --no-scripts --prefer-dist \
`if [ "$TESTING" != "true" ]; then echo "--no-dev"; fi`
-FROM appwrite/base:0.9.2 AS final
+FROM appwrite/base:0.9.3 AS final
LABEL maintainer="team@appwrite.io"
@@ -28,6 +28,8 @@ RUN \
apk add boost boost-dev; \
fi
+RUN apk add libwebp
+
WORKDIR /usr/src/code
COPY --from=composer /usr/local/src/vendor /usr/src/code/vendor
diff --git a/README-CN.md b/README-CN.md
index 5e0cbab4c4..a5eac1be03 100644
--- a/README-CN.md
+++ b/README-CN.md
@@ -67,7 +67,7 @@ docker run -it --rm \
--volume /var/run/docker.sock:/var/run/docker.sock \
--volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \
--entrypoint="install" \
- appwrite/appwrite:1.5.10
+ appwrite/appwrite:1.6.0
```
### Windows
@@ -79,7 +79,7 @@ docker run -it --rm ^
--volume //var/run/docker.sock:/var/run/docker.sock ^
--volume "%cd%"/appwrite:/usr/src/code/appwrite:rw ^
--entrypoint="install" ^
- appwrite/appwrite:1.5.10
+ appwrite/appwrite:1.6.0
```
#### PowerShell
@@ -89,7 +89,7 @@ docker run -it --rm `
--volume /var/run/docker.sock:/var/run/docker.sock `
--volume ${pwd}/appwrite:/usr/src/code/appwrite:rw `
--entrypoint="install" `
- appwrite/appwrite:1.5.10
+ appwrite/appwrite:1.6.0
```
运行后,可以在浏览器上访问 http://localhost 找到 Appwrite 控制台。在非 Linux 的本机主机上完成安装后,服务器可能需要几分钟才能启动。
diff --git a/README.md b/README.md
index 8305d78ef0..a9856a7310 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-> Our Appwrite Init event has concluded. You can check out all the new and upcoming features [on our Init website](https://appwrite.io/init) 🚀
+> Appwrite Init has concluded! You can check out all the latest announcements [on our Init website](https://appwrite.io/init) 🚀
@@ -75,7 +75,7 @@ docker run -it --rm \
--volume /var/run/docker.sock:/var/run/docker.sock \
--volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \
--entrypoint="install" \
- appwrite/appwrite:1.5.10
+ appwrite/appwrite:1.6.0
```
### Windows
@@ -87,7 +87,7 @@ docker run -it --rm ^
--volume //var/run/docker.sock:/var/run/docker.sock ^
--volume "%cd%"/appwrite:/usr/src/code/appwrite:rw ^
--entrypoint="install" ^
- appwrite/appwrite:1.5.10
+ appwrite/appwrite:1.6.0
```
#### PowerShell
@@ -97,7 +97,7 @@ docker run -it --rm `
--volume /var/run/docker.sock:/var/run/docker.sock `
--volume ${pwd}/appwrite:/usr/src/code/appwrite:rw `
--entrypoint="install" `
- appwrite/appwrite:1.5.10
+ appwrite/appwrite:1.6.0
```
Once the Docker installation is complete, go to http://localhost to access the Appwrite console from your browser. Please note that on non-Linux native hosts, the server might take a few minutes to start after completing the installation.
@@ -134,6 +134,12 @@ Choose from one of the providers below:
Akamai Compute
+
diff --git a/app/cli.php b/app/cli.php
index 9d0d069513..23502ec402 100644
--- a/app/cli.php
+++ b/app/cli.php
@@ -7,130 +7,214 @@ use Appwrite\Event\Delete;
use Appwrite\Event\Func;
use Appwrite\Platform\Appwrite;
use Appwrite\Runtimes\Runtimes;
-use Swoole\Runtime;
-use Utopia\CLI\Adapters\Swoole as SwooleCLI;
+use Utopia\Cache\Adapter\Sharding;
+use Utopia\Cache\Cache;
+use Utopia\CLI\CLI;
use Utopia\CLI\Console;
use Utopia\Config\Config;
+use Utopia\Database\Database;
+use Utopia\Database\Document;
use Utopia\Database\Validator\Authorization;
-use Utopia\DI\Dependency;
+use Utopia\DSN\DSN;
use Utopia\Logger\Log;
use Utopia\Platform\Service;
+use Utopia\Pools\Group;
use Utopia\Queue\Connection;
use Utopia\Registry\Registry;
use Utopia\System\System;
-global $registry, $container;
-
-Runtime::enableCoroutine(SWOOLE_HOOK_ALL);
-
// overwriting runtimes to be architectur agnostic for CLI
Config::setParam('runtimes', (new Runtimes('v4'))->getAll(supported: false));
// require controllers after overwriting runtimes
require_once __DIR__ . '/controllers/general.php';
-/**
- * @var Registry $registry
- * @var Container $container
- */
-$context = new Dependency();
-$register = new Dependency();
-$logError = new Dependency();
-$queueForDeletes = new Dependency();
-$queueForFunctions = new Dependency();
-$queueForCertificates = new Dependency();
+Authorization::disable();
-$context
- ->setName('context')
- ->setCallback(fn () => $container);
+CLI::setResource('register', fn () => $register);
-$register
- ->setName('register')
- ->setCallback(function () use (&$registry): Registry {
- return $registry;
- });
+CLI::setResource('cache', function ($pools) {
+ $list = Config::getParam('pools-cache', []);
+ $adapters = [];
-$queueForFunctions
- ->setName('queueForFunctions')
- ->inject('queue')
- ->setCallback(function (Connection $queue) {
- return new Func($queue);
- });
+ foreach ($list as $value) {
+ $adapters[] = $pools
+ ->get($value)
+ ->pop()
+ ->getResource()
+ ;
+ }
+ return new Cache(new Sharding($adapters));
+}, ['pools']);
-$queueForDeletes
- ->setName('queueForDeletes')
- ->inject('queue')
- ->setCallback(function (Connection $queue) {
- return new Delete($queue);
- });
+CLI::setResource('pools', function (Registry $register) {
+ return $register->get('pools');
+}, ['register']);
-$queueForCertificates
- ->setName('queueForCertificates')
- ->inject('queue')
- ->setCallback(function (Connection $queue) {
- return new Certificate($queue);
- });
+CLI::setResource('dbForConsole', function ($pools, $cache) {
+ $sleep = 3;
+ $maxAttempts = 5;
+ $attempts = 0;
+ $ready = false;
-$logError
- ->setName('logError')
- ->inject('register')
- ->setCallback(function (Registry $register) {
- return function (Throwable $error, string $namespace, string $action) use ($register) {
- $logger = $register->get('logger');
+ do {
+ $attempts++;
+ try {
+ // Prepare database connection
+ $dbAdapter = $pools
+ ->get('console')
+ ->pop()
+ ->getResource();
- if ($logger) {
- $version = System::getEnv('_APP_VERSION', 'UNKNOWN');
+ $dbForConsole = new Database($dbAdapter, $cache);
- $log = new Log();
- $log->setNamespace($namespace);
- $log->setServer(\gethostname());
- $log->setVersion($version);
- $log->setType(Log::TYPE_ERROR);
- $log->setMessage($error->getMessage());
+ $dbForConsole
+ ->setNamespace('_console')
+ ->setMetadata('host', \gethostname())
+ ->setMetadata('project', 'console');
- $log->addTag('code', $error->getCode());
- $log->addTag('verboseType', get_class($error));
+ // Ensure tables exist
+ $collections = Config::getParam('collections', [])['console'];
+ $last = \array_key_last($collections);
- $log->addExtra('file', $error->getFile());
- $log->addExtra('line', $error->getLine());
- $log->addExtra('trace', $error->getTraceAsString());
- $log->addExtra('trace', $error->getTraceAsString());
-
- $log->setAction($action);
-
- $isProduction = System::getEnv('_APP_ENV', 'development') === 'production';
-
- $log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING);
-
- $responseCode = $logger->addLog($log);
- Console::info('Usage stats log pushed with status code: ' . $responseCode);
+ if (!($dbForConsole->exists($dbForConsole->getDatabase(), $last))) { /** TODO cache ready variable using registry */
+ throw new Exception('Tables not ready yet.');
}
- Console::warning("Failed: {$error->getMessage()}");
- Console::warning($error->getTraceAsString());
- };
- });
+ $ready = true;
+ } catch (\Throwable $err) {
+ Console::warning($err->getMessage());
+ $pools->get('console')->reclaim();
+ sleep($sleep);
+ }
+ } while ($attempts < $maxAttempts && !$ready);
-$container->set($context);
-$container->set($logError);
-$container->set($register);
-$container->set($queueForDeletes);
-$container->set($queueForFunctions);
-$container->set($queueForCertificates);
+ if (!$ready) {
+ throw new Exception("Console is not ready yet. Please try again later.");
+ }
+
+ return $dbForConsole;
+}, ['pools', 'cache']);
+
+CLI::setResource('getProjectDB', function (Group $pools, Database $dbForConsole, $cache) {
+ $databases = []; // TODO: @Meldiron This should probably be responsibility of utopia-php/pools
+
+ return function (Document $project) use ($pools, $dbForConsole, $cache, &$databases) {
+ if ($project->isEmpty() || $project->getId() === 'console') {
+ return $dbForConsole;
+ }
+
+ try {
+ $dsn = new DSN($project->getAttribute('database'));
+ } catch (\InvalidArgumentException) {
+ // TODO: Temporary until all projects are using shared tables
+ $dsn = new DSN('mysql://' . $project->getAttribute('database'));
+ }
+
+ if (isset($databases[$dsn->getHost()])) {
+ $database = $databases[$dsn->getHost()];
+
+ if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) {
+ $database
+ ->setSharedTables(true)
+ ->setTenant($project->getInternalId())
+ ->setNamespace($dsn->getParam('namespace'));
+ } else {
+ $database
+ ->setSharedTables(false)
+ ->setTenant(null)
+ ->setNamespace('_' . $project->getInternalId());
+ }
+
+ return $database;
+ }
+
+ $dbAdapter = $pools
+ ->get($dsn->getHost())
+ ->pop()
+ ->getResource();
+
+ $database = new Database($dbAdapter, $cache);
+
+ $databases[$dsn->getHost()] = $database;
+
+ if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) {
+ $database
+ ->setSharedTables(true)
+ ->setTenant($project->getInternalId())
+ ->setNamespace($dsn->getParam('namespace'));
+ } else {
+ $database
+ ->setSharedTables(false)
+ ->setTenant(null)
+ ->setNamespace('_' . $project->getInternalId());
+ }
+
+ $database
+ ->setMetadata('host', \gethostname())
+ ->setMetadata('project', $project->getId());
+
+ return $database;
+ };
+}, ['pools', 'dbForConsole', 'cache']);
+
+CLI::setResource('queue', function (Group $pools) {
+ return $pools->get('queue')->pop()->getResource();
+}, ['pools']);
+CLI::setResource('queueForFunctions', function (Connection $queue) {
+ return new Func($queue);
+}, ['queue']);
+CLI::setResource('queueForDeletes', function (Connection $queue) {
+ return new Delete($queue);
+}, ['queue']);
+CLI::setResource('queueForCertificates', function (Connection $queue) {
+ return new Certificate($queue);
+}, ['queue']);
+CLI::setResource('logError', function (Registry $register) {
+ return function (Throwable $error, string $namespace, string $action) use ($register) {
+ $logger = $register->get('logger');
+
+ if ($logger) {
+ $version = System::getEnv('_APP_VERSION', 'UNKNOWN');
+
+ $log = new Log();
+ $log->setNamespace($namespace);
+ $log->setServer(\gethostname());
+ $log->setVersion($version);
+ $log->setType(Log::TYPE_ERROR);
+ $log->setMessage($error->getMessage());
+
+ $log->addTag('code', $error->getCode());
+ $log->addTag('verboseType', get_class($error));
+
+ $log->addExtra('file', $error->getFile());
+ $log->addExtra('line', $error->getLine());
+ $log->addExtra('trace', $error->getTraceAsString());
+
+ $log->setAction($action);
+
+ $isProduction = System::getEnv('_APP_ENV', 'development') === 'production';
+ $log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING);
+
+ try {
+ $responseCode = $logger->addLog($log);
+ Console::info('Error log pushed with status code: ' . $responseCode);
+ } catch (Throwable $th) {
+ Console::error('Error pushing log: ' . $th->getMessage());
+ }
+ }
+
+ Console::warning("Failed: {$error->getMessage()}");
+ Console::warning($error->getTraceAsString());
+ };
+}, ['register']);
$platform = new Appwrite();
-$platform->init(Service::TYPE_TASK, ['adapter' => new SwooleCLI(1)]);
+$platform->init(Service::TYPE_TASK);
$cli = $platform->getCli();
-$cli
- ->init()
- ->inject('authorization')
- ->action(function (Authorization $authorization) {
- $authorization->disable();
- });
-
$cli
->error()
->inject('error')
@@ -138,6 +222,4 @@ $cli
Console::error($error->getMessage());
});
-$cli
- ->setContainer($container)
- ->run();
+$cli->run();
diff --git a/app/config/collections.php b/app/config/collections.php
index 56c1761967..1eb286cf8f 100644
--- a/app/config/collections.php
+++ b/app/config/collections.php
@@ -4109,13 +4109,24 @@ $projectCollections = array_merge([
'$id' => ID::custom('source'),
'type' => Database::VAR_STRING,
'format' => '',
- 'size' => 8192,
+ 'size' => 8192, // reduce size
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
+ [
+ '$id' => ID::custom('destination'),
+ 'type' => Database::VAR_STRING,
+ 'format' => '',
+ 'size' => Database::LENGTH_KEY,
+ 'signed' => true,
+ 'required' => false, // make true after patch script
+ 'default' => null,
+ 'array' => false,
+ 'filters' => [],
+ ],
[
'$id' => ID::custom('credentials'),
'type' => Database::VAR_STRING,
@@ -4138,6 +4149,28 @@ $projectCollections = array_merge([
'array' => true,
'filters' => [],
],
+ [
+ '$id' => ID::custom('resourceId'),
+ 'type' => Database::VAR_STRING,
+ 'format' => '',
+ 'size' => Database::LENGTH_KEY,
+ 'signed' => true,
+ 'required' => false,
+ 'default' => null,
+ 'array' => false,
+ 'filters' => [],
+ ],
+ [
+ '$id' => ID::custom('resourceType'),
+ 'type' => Database::VAR_STRING,
+ 'format' => '',
+ 'size' => Database::LENGTH_KEY,
+ 'signed' => true,
+ 'required' => false,
+ 'default' => null,
+ 'array' => false,
+ 'filters' => [],
+ ],
[
'$id' => ID::custom('statusCounters'),
'type' => Database::VAR_STRING,
diff --git a/app/config/function-templates.php b/app/config/function-templates.php
index 4b58cc44be..db26ff2c19 100644
--- a/app/config/function-templates.php
+++ b/app/config/function-templates.php
@@ -82,7 +82,7 @@ return [
'providerOwner' => 'appwrite',
'providerVersion' => '0.2.*',
'variables' => [],
- 'scopes' => ["users.read"]
+ 'scopes' => ['users.read']
],
[
'icon' => 'icon-upstash',
@@ -125,7 +125,8 @@ return [
'required' => true,
'type' => 'password'
]
- ]
+ ],
+ 'scopes' => []
],
[
'icon' => 'icon-redis',
@@ -167,7 +168,8 @@ return [
'required' => true,
'type' => 'password'
]
- ]
+ ],
+ 'scopes' => []
],
[
'icon' => 'icon-neo4j',
@@ -217,7 +219,8 @@ return [
'required' => true,
'type' => 'password'
]
- ]
+ ],
+ 'scopes' => []
],
[
'icon' => 'icon-mongodb',
@@ -253,7 +256,8 @@ return [
'required' => true,
'type' => 'password'
]
- ]
+ ],
+ 'scopes' => []
],
[
'icon' => 'icon-neon',
@@ -320,7 +324,8 @@ return [
'required' => true,
'type' => 'text'
]
- ]
+ ],
+ 'scopes' => []
],
[
'icon' => 'icon-open-ai',
@@ -380,7 +385,8 @@ return [
'required' => false,
'type' => 'number'
]
- ]
+ ],
+ 'scopes' => []
],
[
'icon' => 'icon-discord',
@@ -442,7 +448,8 @@ return [
'required' => true,
'type' => 'password'
]
- ]
+ ],
+ 'scopes' => []
],
[
'icon' => 'icon-perspective-api',
@@ -476,7 +483,8 @@ return [
'required' => true,
'type' => 'password'
]
- ]
+ ],
+ 'scopes' => []
],
[
'icon' => 'icon-pangea',
@@ -523,7 +531,8 @@ return [
'required' => true,
'type' => 'password'
]
- ]
+ ],
+ 'scopes' => []
],
[
'icon' => 'icon-document',
@@ -543,7 +552,8 @@ return [
'providerRepositoryId' => 'templates',
'providerOwner' => 'appwrite',
'providerVersion' => '0.2.*',
- 'variables' => []
+ 'variables' => [],
+ 'scopes' => []
],
[
'icon' => 'icon-github',
@@ -586,7 +596,8 @@ return [
'required' => true,
'type' => 'password'
]
- ]
+ ],
+ 'scopes' => []
],
[
'icon' => 'icon-bookmark',
@@ -637,7 +648,7 @@ return [
'type' => 'url'
]
],
- 'scopes' => ["databases.read", "databases.write", "collections.write", "attributes.write", "documents.read", "documents.write"]
+ 'scopes' => ['databases.read', 'databases.write', 'collections.write', 'attributes.write', 'documents.read', 'documents.write']
],
[
'icon' => 'icon-algolia',
@@ -718,7 +729,7 @@ return [
'type' => 'password'
],
],
- 'scopes' => ["databases.read", "collections.read", "documents.read"]
+ 'scopes' => ['databases.read', 'collections.read', 'documents.read']
],
[
'icon' => 'icon-meilisearch',
@@ -811,7 +822,7 @@ return [
'type' => 'text'
],
],
- 'scopes' => ["databases.read", "collections.read", "documents.read"]
+ 'scopes' => ['databases.read', 'collections.read', 'documents.read']
],
[
'icon' => 'icon-vonage',
@@ -896,7 +907,8 @@ return [
'required' => true,
'type' => 'phone'
]
- ]
+ ],
+ 'scopes' => []
],
[
'icon' => 'icon-bell',
@@ -951,7 +963,8 @@ return [
'required' => true,
'type' => 'url'
]
- ]
+ ],
+ 'scopes' => []
],
[
'icon' => 'icon-mail',
@@ -1033,7 +1046,8 @@ return [
'required' => false,
'type' => 'text'
]
- ]
+ ],
+ 'scopes' => []
],
[
'icon' => 'icon-stripe',
@@ -1074,7 +1088,7 @@ return [
'type' => 'password'
]
],
- 'scopes' => ["users.read", "sessions.write", "users.write"]
+ 'scopes' => ['users.read', 'sessions.write', 'users.write']
],
[
'icon' => 'icon-stripe',
@@ -1131,7 +1145,7 @@ return [
'type' => 'text'
]
],
- 'scopes' => ["databases.read", "databases.write", "collections.write", "attributes.write", "documents.read", "documents.write"]
+ 'scopes' => ['databases.read', 'databases.write', 'collections.write', 'attributes.write', 'documents.read', 'documents.write']
],
[
'icon' => 'icon-chat',
@@ -1164,7 +1178,8 @@ return [
'required' => true,
'type' => 'password'
]
- ]
+ ],
+ 'scopes' => []
],
[
'icon' => 'icon-translate',
@@ -1197,7 +1212,8 @@ return [
'required' => true,
'type' => 'password'
]
- ]
+ ],
+ 'scopes' => []
],
[
'icon' => 'icon-eye',
@@ -1255,7 +1271,7 @@ return [
'type' => 'password'
]
],
- 'scopes' => ["databases.read", "databases.write", "collections.read", "collections.write", "attributes.write", "documents.read", "documents.write", "buckets.read", "buckets.write", "files.read"]
+ 'scopes' => ['databases.read', 'databases.write', 'collections.read', 'collections.write', 'attributes.write', 'documents.read', 'documents.write', 'buckets.read', 'buckets.write', 'files.read']
],
[
'icon' => 'icon-eye',
@@ -1313,7 +1329,7 @@ return [
'type' => 'password'
]
],
- "scopes" => ["databases.read", "databases.write", "collections.read", "collections.write", "attributes.write", "documents.read", "documents.write", "buckets.read", "buckets.write", "files.read"]
+ 'scopes' => ['databases.read', 'databases.write', 'collections.read', 'collections.write', 'attributes.write', 'documents.read', 'documents.write', 'buckets.read', 'buckets.write', 'files.read']
],
[
'icon' => 'icon-text',
@@ -1371,7 +1387,7 @@ return [
'type' => 'password'
]
],
- "scopes" => ["databases.read", "databases.write", "collections.read", "collections.write", "attributes.write", "documents.read", "documents.write", "buckets.read", "buckets.write", "files.read"]
+ 'scopes' => ['databases.read', 'databases.write', 'collections.read', 'collections.write', 'attributes.write', 'documents.read', 'documents.write', 'buckets.read', 'buckets.write', 'files.read']
],
[
'icon' => 'icon-chat',
@@ -1429,7 +1445,7 @@ return [
'type' => 'password'
]
],
- "scopes" => ["buckets.read", "buckets.write", "files.read", "files.write"]
+ 'scopes' => ['buckets.read', 'buckets.write', 'files.read', 'files.write']
],
[
'icon' => 'icon-chip',
@@ -1463,7 +1479,8 @@ return [
'required' => true,
'type' => 'password'
]
- ]
+ ],
+ 'scopes' => []
],
[
'icon' => 'icon-chip',
@@ -1505,7 +1522,7 @@ return [
'type' => 'text'
]
],
- "scopes" => ["buckets.write", "files.read", "files.write"]
+ 'scopes' => ['buckets.write', 'files.read', 'files.write']
],
[
'icon' => 'icon-chip',
@@ -1579,7 +1596,8 @@ return [
'required' => true,
'type' => 'password'
]
- ]
+ ],
+ 'scopes' => []
],
[
'icon' => 'icon-document-search',
@@ -1642,7 +1660,7 @@ return [
'type' => 'text'
]
],
- "scopes" => ["databases.read", "collections.read", "documents.read"]
+ 'scopes' => ['databases.read', 'collections.read', 'documents.read']
],
[
'icon' => 'icon-chip',
@@ -1705,7 +1723,7 @@ return [
'type' => 'text'
]
],
- "scopes" => ["databases.read", "collections.read", "documents.read"]
+ 'scopes' => ['databases.read', 'collections.read', 'documents.read']
],
[
'icon' => 'icon-chat',
@@ -1760,7 +1778,7 @@ return [
'type' => 'text'
]
],
- "scopes" => ["buckets.read", "buckets.write", "files.read", "files.write"]
+ 'scopes' => ['buckets.read', 'buckets.write', 'files.read', 'files.write']
],
[
'icon' => 'icon-chip',
@@ -1801,7 +1819,7 @@ return [
'type' => 'text'
]
],
- "scopes" => ["buckets.read", "buckets.write", "files.read", "files.write"]
+ 'scopes' => ['buckets.read', 'buckets.write', 'files.read', 'files.write']
],
[
'icon' => 'icon-chip',
@@ -1841,7 +1859,8 @@ return [
'required' => false,
'type' => 'number'
]
- ]
+ ],
+ 'scopes' => []
],
[
'icon' => 'icon-music-note',
@@ -1883,7 +1902,7 @@ return [
'type' => 'password'
]
],
- "scopes" => ["buckets.read", "buckets.write", "files.read", "files.write"]
+ 'scopes' => ['buckets.read', 'buckets.write', 'files.read', 'files.write']
],
[
'icon' => 'icon-chip',
@@ -1917,7 +1936,8 @@ return [
'required' => true,
'type' => 'password'
]
- ]
+ ],
+ 'scopes' => []
],
[
'icon' => 'icon-currency-dollar',
@@ -1972,7 +1992,7 @@ return [
'type' => 'text'
]
],
- "scopes" => ["users.read", "users.write"]
+ 'scopes' => ['users.read', 'users.write']
],
[
'icon' => 'icon-currency-dollar',
@@ -2043,6 +2063,6 @@ return [
'type' => 'text'
]
],
- "scopes" => ["users.read", "users.write"]
+ 'scopes' => ['users.read', 'users.write']
]
];
diff --git a/app/config/platforms.php b/app/config/platforms.php
index 40cea19fd3..e7eb1180cd 100644
--- a/app/config/platforms.php
+++ b/app/config/platforms.php
@@ -1,9 +1,5 @@
[
'key' => APP_PLATFORM_CLIENT,
diff --git a/app/config/roles.php b/app/config/roles.php
index 65b9643b89..fae97895b8 100644
--- a/app/config/roles.php
+++ b/app/config/roles.php
@@ -17,7 +17,6 @@ $member = [
'files.read',
'files.write',
'projects.read',
- 'projects.write',
'locale.read',
'avatars.read',
'execution.read',
@@ -49,6 +48,7 @@ $admins = [
'collections.write',
'platforms.read',
'platforms.write',
+ 'projects.write',
'keys.read',
'keys.write',
'webhooks.read',
@@ -75,7 +75,7 @@ $admins = [
'topics.write',
'topics.read',
'subscribers.write',
- 'subscribers.read'
+ 'subscribers.read',
];
return [
diff --git a/app/config/specs/open-api3-1.6.x-client.json b/app/config/specs/open-api3-1.6.x-client.json
index 8a9967090d..021ae27c45 100644
--- a/app/config/specs/open-api3-1.6.x-client.json
+++ b/app/config/specs/open-api3-1.6.x-client.json
@@ -166,7 +166,7 @@
"tags": [
"account"
],
- "description": "Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. For security measures, user password is required to complete this request.\nThis endpoint can also be used to convert an anonymous account to a normal one, by passing an email address and a new password.\n",
+ "description": "Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. For security measures, user password is required to complete this request.\r\nThis endpoint can also be used to convert an anonymous account to a normal one, by passing an email address and a new password.\r\n",
"responses": {
"200": {
"description": "User",
@@ -239,7 +239,7 @@
},
"\/account\/identities": {
"get": {
- "summary": "List Identities",
+ "summary": "List identities",
"operationId": "accountListIdentities",
"tags": [
"account"
@@ -556,7 +556,7 @@
},
"\/account\/mfa\/authenticators\/{type}": {
"post": {
- "summary": "Create Authenticator",
+ "summary": "Create authenticator",
"operationId": "accountCreateMfaAuthenticator",
"tags": [
"account"
@@ -624,7 +624,7 @@
]
},
"put": {
- "summary": "Verify Authenticator",
+ "summary": "Verify authenticator",
"operationId": "accountUpdateMfaAuthenticator",
"tags": [
"account"
@@ -711,7 +711,7 @@
}
},
"delete": {
- "summary": "Delete Authenticator",
+ "summary": "Delete authenticator",
"operationId": "accountDeleteMfaAuthenticator",
"tags": [
"account"
@@ -774,7 +774,7 @@
},
"\/account\/mfa\/challenge": {
"post": {
- "summary": "Create MFA Challenge",
+ "summary": "Create MFA challenge",
"operationId": "accountCreateMfaChallenge",
"tags": [
"account"
@@ -850,7 +850,7 @@
}
},
"put": {
- "summary": "Create MFA Challenge (confirmation)",
+ "summary": "Create MFA challenge (confirmation)",
"operationId": "accountUpdateMfaChallenge",
"tags": [
"account"
@@ -928,7 +928,7 @@
},
"\/account\/mfa\/factors": {
"get": {
- "summary": "List Factors",
+ "summary": "List factors",
"operationId": "accountListMfaFactors",
"tags": [
"account"
@@ -981,7 +981,7 @@
},
"\/account\/mfa\/recovery-codes": {
"get": {
- "summary": "Get MFA Recovery Codes",
+ "summary": "Get MFA recovery codes",
"operationId": "accountGetMfaRecoveryCodes",
"tags": [
"account"
@@ -1032,7 +1032,7 @@
]
},
"post": {
- "summary": "Create MFA Recovery Codes",
+ "summary": "Create MFA recovery codes",
"operationId": "accountCreateMfaRecoveryCodes",
"tags": [
"account"
@@ -1083,7 +1083,7 @@
]
},
"patch": {
- "summary": "Regenerate MFA Recovery Codes",
+ "summary": "Regenerate MFA recovery codes",
"operationId": "accountUpdateMfaRecoveryCodes",
"tags": [
"account"
@@ -1570,7 +1570,7 @@
"tags": [
"account"
],
- "description": "Use this endpoint to complete the user account password reset. Both the **userId** and **secret** arguments will be passed as query parameters to the redirect URL you have provided when sending your request to the [POST \/account\/recovery](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createRecovery) endpoint.\n\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.",
+ "description": "Use this endpoint to complete the user account password reset. Both the **userId** and **secret** arguments will be passed as query parameters to the redirect URL you have provided when sending your request to the [POST \/account\/recovery](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createRecovery) endpoint.\r\n\r\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.",
"responses": {
"200": {
"description": "Token",
@@ -1802,7 +1802,7 @@
"tags": [
"account"
],
- "description": "Allow the user to login into their account by providing a valid email and password combination. This route will create a new session for the user.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
+ "description": "Allow the user to login into their account by providing a valid email and password combination. This route will create a new session for the user.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
"responses": {
"201": {
"description": "Session",
@@ -1954,7 +1954,7 @@
"tags": [
"account"
],
- "description": "Allow the user to login to their account using the OAuth2 provider of their choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed.\n\nIf there is already an active session, the new session will be attached to the logged-in account. If there are no active sessions, the server will attempt to look for a user with the same email address as the email received from the OAuth2 provider and attach the new session to the existing user. If no matching user is found - the server will create a new user.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\n",
+ "description": "Allow the user to login to their account using the OAuth2 provider of their choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed.\r\n\r\nIf there is already an active session, the new session will be attached to the logged-in account. If there are no active sessions, the server will attempt to look for a user with the same email address as the email received from the OAuth2 provider and attach the new session to the existing user. If no matching user is found - the server will create a new user.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\r\n",
"responses": {
"301": {
"description": "File"
@@ -2703,7 +2703,7 @@
"tags": [
"account"
],
- "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's email is valid for 15 minutes.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
+ "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's email is valid for 15 minutes.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
"responses": {
"201": {
"description": "Token",
@@ -2784,7 +2784,7 @@
"tags": [
"account"
],
- "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour. If you are on a mobile device you can leave the URL parameter empty, so that the login completion will be handled by your Appwrite instance by default.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\n",
+ "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour. If you are on a mobile device you can leave the URL parameter empty, so that the login completion will be handled by your Appwrite instance by default.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\r\n",
"responses": {
"201": {
"description": "Token",
@@ -2873,7 +2873,7 @@
"tags": [
"account"
],
- "description": "Allow the user to login to their account using the OAuth2 provider of their choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed. \n\nIf authentication succeeds, `userId` and `secret` of a token will be appended to the success URL as query parameters. These can be used to create a new session using the [Create session](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
+ "description": "Allow the user to login to their account using the OAuth2 provider of their choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed. \r\n\r\nIf authentication succeeds, `userId` and `secret` of a token will be appended to the success URL as query parameters. These can be used to create a new session using the [Create session](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
"responses": {
"301": {
"description": "File"
@@ -3009,7 +3009,7 @@
"tags": [
"account"
],
- "description": "Sends the user an SMS with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's phone is valid for 15 minutes.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
+ "description": "Sends the user an SMS with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's phone is valid for 15 minutes.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
"responses": {
"201": {
"description": "Token",
@@ -3088,7 +3088,7 @@
"tags": [
"account"
],
- "description": "Use this endpoint to send a verification message to your user email address to confirm they are the valid owners of that address. Both the **userId** and **secret** arguments will be passed as query parameters to the URL you have provided to be attached to the verification email. The provided URL should redirect the user back to your app and allow you to complete the verification process by verifying both the **userId** and **secret** parameters. Learn more about how to [complete the verification process](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\n\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.\n",
+ "description": "Use this endpoint to send a verification message to your user email address to confirm they are the valid owners of that address. Both the **userId** and **secret** arguments will be passed as query parameters to the URL you have provided to be attached to the verification email. The provided URL should redirect the user back to your app and allow you to complete the verification process by verifying both the **userId** and **secret** parameters. Learn more about how to [complete the verification process](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\r\n\r\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.\r\n",
"responses": {
"201": {
"description": "Token",
@@ -3368,7 +3368,7 @@
"tags": [
"avatars"
],
- "description": "You can use this endpoint to show different browser icons to your users. The code argument receives the browser code as it appears in your user [GET \/account\/sessions](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#getSessions) endpoint. Use width, height and quality arguments to change the output settings.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.",
+ "description": "You can use this endpoint to show different browser icons to your users. The code argument receives the browser code as it appears in your user [GET \/account\/sessions](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#getSessions) endpoint. Use width, height and quality arguments to change the output settings.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.",
"responses": {
"200": {
"description": "Image"
@@ -3496,7 +3496,7 @@
"tags": [
"avatars"
],
- "description": "The credit card endpoint will return you the icon of the credit card provider you need. Use width, height and quality arguments to change the output settings.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n",
+ "description": "The credit card endpoint will return you the icon of the credit card provider you need. Use width, height and quality arguments to change the output settings.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n",
"responses": {
"200": {
"description": "Image"
@@ -3628,7 +3628,7 @@
"tags": [
"avatars"
],
- "description": "Use this endpoint to fetch the favorite icon (AKA favicon) of any remote website URL.\n\nThis endpoint does not follow HTTP redirects.",
+ "description": "Use this endpoint to fetch the favorite icon (AKA favicon) of any remote website URL.\r\n\r\nThis endpoint does not follow HTTP redirects.",
"responses": {
"200": {
"description": "Image"
@@ -3688,7 +3688,7 @@
"tags": [
"avatars"
],
- "description": "You can use this endpoint to show different country flags icons to your users. The code argument receives the 2 letter country code. Use width, height and quality arguments to change the output settings. Country codes follow the [ISO 3166-1](https:\/\/en.wikipedia.org\/wiki\/ISO_3166-1) standard.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n",
+ "description": "You can use this endpoint to show different country flags icons to your users. The code argument receives the 2 letter country code. Use width, height and quality arguments to change the output settings. Country codes follow the [ISO 3166-1](https:\/\/en.wikipedia.org\/wiki\/ISO_3166-1) standard.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n",
"responses": {
"200": {
"description": "Image"
@@ -4178,7 +4178,7 @@
"tags": [
"avatars"
],
- "description": "Use this endpoint to fetch a remote image URL and crop it to any image size you want. This endpoint is very useful if you need to crop and display remote images in your app or in case you want to make sure a 3rd party image is properly served using a TLS protocol.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 400x400px.\n\nThis endpoint does not follow HTTP redirects.",
+ "description": "Use this endpoint to fetch a remote image URL and crop it to any image size you want. This endpoint is very useful if you need to crop and display remote images in your app or in case you want to make sure a 3rd party image is properly served using a TLS protocol.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 400x400px.\r\n\r\nThis endpoint does not follow HTTP redirects.",
"responses": {
"200": {
"description": "Image"
@@ -4262,7 +4262,7 @@
"tags": [
"avatars"
],
- "description": "Use this endpoint to show your user initials avatar icon on your website or app. By default, this route will try to print your logged-in user name or email initials. You can also overwrite the user name if you pass the 'name' parameter. If no name is given and no user is logged, an empty avatar will be returned.\n\nYou can use the color and background params to change the avatar colors. By default, a random theme will be selected. The random theme will persist for the user's initials when reloading the same theme will always return for the same initials.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n",
+ "description": "Use this endpoint to show your user initials avatar icon on your website or app. By default, this route will try to print your logged-in user name or email initials. You can also overwrite the user name if you pass the 'name' parameter. If no name is given and no user is logged, an empty avatar will be returned.\r\n\r\nYou can use the color and background params to change the avatar colors. By default, a random theme will be selected. The random theme will persist for the user's initials when reloading the same theme will always return for the same initials.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n",
"responses": {
"200": {
"description": "Image"
@@ -4356,7 +4356,7 @@
"tags": [
"avatars"
],
- "description": "Converts a given plain text to a QR code image. You can use the query parameters to change the size and style of the resulting image.\n",
+ "description": "Converts a given plain text to a QR code image. You can use the query parameters to change the size and style of the resulting image.\r\n",
"responses": {
"200": {
"description": "Image"
@@ -5319,7 +5319,7 @@
"tags": [
"locale"
],
- "description": "Get the current user location based on IP. Returns an object with user country code, country name, continent name, continent code, ip address and suggested currency. You can use the locale header to get the data in a supported language.\n\n([IP Geolocation by DB-IP](https:\/\/db-ip.com))",
+ "description": "Get the current user location based on IP. Returns an object with user country code, country name, continent name, continent code, ip address and suggested currency. You can use the locale header to get the data in a supported language.\r\n\r\n([IP Geolocation by DB-IP](https:\/\/db-ip.com))",
"responses": {
"200": {
"description": "Locale",
@@ -5368,7 +5368,7 @@
},
"\/locale\/codes": {
"get": {
- "summary": "List Locale Codes",
+ "summary": "List locale codes",
"operationId": "localeListCodes",
"tags": [
"locale"
@@ -6001,7 +6001,7 @@
"tags": [
"storage"
],
- "description": "Create a new file. Before using this route, you should create a new bucket resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/storage#storageCreateBucket) API or directly from your Appwrite console.\n\nLarger files should be uploaded using multiple requests with the [content-range](https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Content-Range) header to send a partial request with a maximum supported chunk of `5MB`. The `content-range` header values should always be in bytes.\n\nWhen the first request is sent, the server will return the **File** object, and the subsequent part request must include the file's **id** in `x-appwrite-id` header to allow the server to know that the partial upload is for the existing file and not for a new one.\n\nIf you're creating a new file using one of the Appwrite SDKs, all the chunking logic will be managed by the SDK internally.\n",
+ "description": "Create a new file. Before using this route, you should create a new bucket resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/storage#storageCreateBucket) API or directly from your Appwrite console.\r\n\r\nLarger files should be uploaded using multiple requests with the [content-range](https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Content-Range) header to send a partial request with a maximum supported chunk of `5MB`. The `content-range` header values should always be in bytes.\r\n\r\nWhen the first request is sent, the server will return the **File** object, and the subsequent part request must include the file's **id** in `x-appwrite-id` header to allow the server to know that the partial upload is for the existing file and not for a new one.\r\n\r\nIf you're creating a new file using one of the Appwrite SDKs, all the chunking logic will be managed by the SDK internally.\r\n",
"responses": {
"201": {
"description": "File",
@@ -6268,7 +6268,7 @@
}
},
"delete": {
- "summary": "Delete File",
+ "summary": "Delete file",
"operationId": "storageDeleteFile",
"tags": [
"storage"
@@ -6610,7 +6610,8 @@
"jpeg",
"gif",
"png",
- "webp"
+ "webp",
+ "avif"
],
"x-enum-name": "ImageFormat",
"x-enum-keys": [],
@@ -7156,7 +7157,7 @@
"tags": [
"teams"
],
- "description": "Invite a new member to join your team. Provide an ID for existing users, or invite unregistered users using an email or phone number. If initiated from a Client SDK, Appwrite will send an email or sms with a link to join the team to the invited user, and an account will be created for them if one doesn't exist. If initiated from a Server SDK, the new member will be added automatically to the team.\n\nYou only need to provide one of a user ID, email, or phone number. Appwrite will prioritize accepting the user ID > email > phone number if you provide more than one of these parameters.\n\nUse the `url` parameter to redirect the user from the invitation email to your app. After the user is redirected, use the [Update Team Membership Status](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/teams#updateMembershipStatus) endpoint to allow the user to accept the invitation to the team. \n\nPlease note that to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) Appwrite will accept the only redirect URLs under the domains you have added as a platform on the Appwrite Console.\n",
+ "description": "Invite a new member to join your team. Provide an ID for existing users, or invite unregistered users using an email or phone number. If initiated from a Client SDK, Appwrite will send an email or sms with a link to join the team to the invited user, and an account will be created for them if one doesn't exist. If initiated from a Server SDK, the new member will be added automatically to the team.\r\n\r\nYou only need to provide one of a user ID, email, or phone number. Appwrite will prioritize accepting the user ID > email > phone number if you provide more than one of these parameters.\r\n\r\nUse the `url` parameter to redirect the user from the invitation email to your app. After the user is redirected, use the [Update Team Membership Status](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/teams#updateMembershipStatus) endpoint to allow the user to accept the invitation to the team. \r\n\r\nPlease note that to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) Appwrite will accept the only redirect URLs under the domains you have added as a platform on the Appwrite Console.\r\n",
"responses": {
"201": {
"description": "Membership",
@@ -7343,7 +7344,7 @@
"tags": [
"teams"
],
- "description": "Modify the roles of a team member. Only team members with the owner role have access to this endpoint. Learn more about [roles and permissions](https:\/\/appwrite.io\/docs\/permissions).\n",
+ "description": "Modify the roles of a team member. Only team members with the owner role have access to this endpoint. Learn more about [roles and permissions](https:\/\/appwrite.io\/docs\/permissions).\r\n",
"responses": {
"200": {
"description": "Membership",
@@ -7508,7 +7509,7 @@
"tags": [
"teams"
],
- "description": "Use this endpoint to allow a user to accept an invitation to join a team after being redirected back to your app from the invitation email received by the user.\n\nIf the request is successful, a session for the user is automatically created.\n",
+ "description": "Use this endpoint to allow a user to accept an invitation to join a team after being redirected back to your app from the invitation email received by the user.\r\n\r\nIf the request is successful, a session for the user is automatically created.\r\n",
"responses": {
"200": {
"description": "Membership",
@@ -9405,7 +9406,7 @@
"responseBody": {
"type": "string",
"description": "HTTP response body. This will return empty unless execution is created as synchronous.",
- "x-example": "Developers are awesome."
+ "x-example": ""
},
"responseHeaders": {
"type": "array",
diff --git a/app/config/specs/open-api3-1.6.x-console.json b/app/config/specs/open-api3-1.6.x-console.json
index 07749889d8..04e7c76e13 100644
--- a/app/config/specs/open-api3-1.6.x-console.json
+++ b/app/config/specs/open-api3-1.6.x-console.json
@@ -206,7 +206,7 @@
"tags": [
"account"
],
- "description": "Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. For security measures, user password is required to complete this request.\nThis endpoint can also be used to convert an anonymous account to a normal one, by passing an email address and a new password.\n",
+ "description": "Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. For security measures, user password is required to complete this request.\r\nThis endpoint can also be used to convert an anonymous account to a normal one, by passing an email address and a new password.\r\n",
"responses": {
"200": {
"description": "User",
@@ -278,7 +278,7 @@
},
"\/account\/identities": {
"get": {
- "summary": "List Identities",
+ "summary": "List identities",
"operationId": "accountListIdentities",
"tags": [
"account"
@@ -591,7 +591,7 @@
},
"\/account\/mfa\/authenticators\/{type}": {
"post": {
- "summary": "Create Authenticator",
+ "summary": "Create authenticator",
"operationId": "accountCreateMfaAuthenticator",
"tags": [
"account"
@@ -658,7 +658,7 @@
]
},
"put": {
- "summary": "Verify Authenticator",
+ "summary": "Verify authenticator",
"operationId": "accountUpdateMfaAuthenticator",
"tags": [
"account"
@@ -744,7 +744,7 @@
}
},
"delete": {
- "summary": "Delete Authenticator",
+ "summary": "Delete authenticator",
"operationId": "accountDeleteMfaAuthenticator",
"tags": [
"account"
@@ -806,7 +806,7 @@
},
"\/account\/mfa\/challenge": {
"post": {
- "summary": "Create MFA Challenge",
+ "summary": "Create MFA challenge",
"operationId": "accountCreateMfaChallenge",
"tags": [
"account"
@@ -882,7 +882,7 @@
}
},
"put": {
- "summary": "Create MFA Challenge (confirmation)",
+ "summary": "Create MFA challenge (confirmation)",
"operationId": "accountUpdateMfaChallenge",
"tags": [
"account"
@@ -959,7 +959,7 @@
},
"\/account\/mfa\/factors": {
"get": {
- "summary": "List Factors",
+ "summary": "List factors",
"operationId": "accountListMfaFactors",
"tags": [
"account"
@@ -1011,7 +1011,7 @@
},
"\/account\/mfa\/recovery-codes": {
"get": {
- "summary": "Get MFA Recovery Codes",
+ "summary": "Get MFA recovery codes",
"operationId": "accountGetMfaRecoveryCodes",
"tags": [
"account"
@@ -1061,7 +1061,7 @@
]
},
"post": {
- "summary": "Create MFA Recovery Codes",
+ "summary": "Create MFA recovery codes",
"operationId": "accountCreateMfaRecoveryCodes",
"tags": [
"account"
@@ -1111,7 +1111,7 @@
]
},
"patch": {
- "summary": "Regenerate MFA Recovery Codes",
+ "summary": "Regenerate MFA recovery codes",
"operationId": "accountUpdateMfaRecoveryCodes",
"tags": [
"account"
@@ -1591,7 +1591,7 @@
"tags": [
"account"
],
- "description": "Use this endpoint to complete the user account password reset. Both the **userId** and **secret** arguments will be passed as query parameters to the redirect URL you have provided when sending your request to the [POST \/account\/recovery](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createRecovery) endpoint.\n\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.",
+ "description": "Use this endpoint to complete the user account password reset. Both the **userId** and **secret** arguments will be passed as query parameters to the redirect URL you have provided when sending your request to the [POST \/account\/recovery](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createRecovery) endpoint.\r\n\r\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.",
"responses": {
"200": {
"description": "Token",
@@ -1820,7 +1820,7 @@
"tags": [
"account"
],
- "description": "Allow the user to login into their account by providing a valid email and password combination. This route will create a new session for the user.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
+ "description": "Allow the user to login into their account by providing a valid email and password combination. This route will create a new session for the user.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
"responses": {
"201": {
"description": "Session",
@@ -1972,7 +1972,7 @@
"tags": [
"account"
],
- "description": "Allow the user to login to their account using the OAuth2 provider of their choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed.\n\nIf there is already an active session, the new session will be attached to the logged-in account. If there are no active sessions, the server will attempt to look for a user with the same email address as the email received from the OAuth2 provider and attach the new session to the existing user. If no matching user is found - the server will create a new user.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\n",
+ "description": "Allow the user to login to their account using the OAuth2 provider of their choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed.\r\n\r\nIf there is already an active session, the new session will be attached to the logged-in account. If there are no active sessions, the server will attempt to look for a user with the same email address as the email received from the OAuth2 provider and attach the new session to the existing user. If no matching user is found - the server will create a new user.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\r\n",
"responses": {
"301": {
"description": "File"
@@ -2714,7 +2714,7 @@
"tags": [
"account"
],
- "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's email is valid for 15 minutes.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
+ "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's email is valid for 15 minutes.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
"responses": {
"201": {
"description": "Token",
@@ -2795,7 +2795,7 @@
"tags": [
"account"
],
- "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour. If you are on a mobile device you can leave the URL parameter empty, so that the login completion will be handled by your Appwrite instance by default.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\n",
+ "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour. If you are on a mobile device you can leave the URL parameter empty, so that the login completion will be handled by your Appwrite instance by default.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\r\n",
"responses": {
"201": {
"description": "Token",
@@ -2884,7 +2884,7 @@
"tags": [
"account"
],
- "description": "Allow the user to login to their account using the OAuth2 provider of their choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed. \n\nIf authentication succeeds, `userId` and `secret` of a token will be appended to the success URL as query parameters. These can be used to create a new session using the [Create session](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
+ "description": "Allow the user to login to their account using the OAuth2 provider of their choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed. \r\n\r\nIf authentication succeeds, `userId` and `secret` of a token will be appended to the success URL as query parameters. These can be used to create a new session using the [Create session](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
"responses": {
"301": {
"description": "File"
@@ -3020,7 +3020,7 @@
"tags": [
"account"
],
- "description": "Sends the user an SMS with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's phone is valid for 15 minutes.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
+ "description": "Sends the user an SMS with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's phone is valid for 15 minutes.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
"responses": {
"201": {
"description": "Token",
@@ -3099,7 +3099,7 @@
"tags": [
"account"
],
- "description": "Use this endpoint to send a verification message to your user email address to confirm they are the valid owners of that address. Both the **userId** and **secret** arguments will be passed as query parameters to the URL you have provided to be attached to the verification email. The provided URL should redirect the user back to your app and allow you to complete the verification process by verifying both the **userId** and **secret** parameters. Learn more about how to [complete the verification process](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\n\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.\n",
+ "description": "Use this endpoint to send a verification message to your user email address to confirm they are the valid owners of that address. Both the **userId** and **secret** arguments will be passed as query parameters to the URL you have provided to be attached to the verification email. The provided URL should redirect the user back to your app and allow you to complete the verification process by verifying both the **userId** and **secret** parameters. Learn more about how to [complete the verification process](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\r\n\r\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.\r\n",
"responses": {
"201": {
"description": "Token",
@@ -3375,7 +3375,7 @@
"tags": [
"avatars"
],
- "description": "You can use this endpoint to show different browser icons to your users. The code argument receives the browser code as it appears in your user [GET \/account\/sessions](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#getSessions) endpoint. Use width, height and quality arguments to change the output settings.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.",
+ "description": "You can use this endpoint to show different browser icons to your users. The code argument receives the browser code as it appears in your user [GET \/account\/sessions](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#getSessions) endpoint. Use width, height and quality arguments to change the output settings.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.",
"responses": {
"200": {
"description": "Image"
@@ -3503,7 +3503,7 @@
"tags": [
"avatars"
],
- "description": "The credit card endpoint will return you the icon of the credit card provider you need. Use width, height and quality arguments to change the output settings.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n",
+ "description": "The credit card endpoint will return you the icon of the credit card provider you need. Use width, height and quality arguments to change the output settings.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n",
"responses": {
"200": {
"description": "Image"
@@ -3635,7 +3635,7 @@
"tags": [
"avatars"
],
- "description": "Use this endpoint to fetch the favorite icon (AKA favicon) of any remote website URL.\n\nThis endpoint does not follow HTTP redirects.",
+ "description": "Use this endpoint to fetch the favorite icon (AKA favicon) of any remote website URL.\r\n\r\nThis endpoint does not follow HTTP redirects.",
"responses": {
"200": {
"description": "Image"
@@ -3695,7 +3695,7 @@
"tags": [
"avatars"
],
- "description": "You can use this endpoint to show different country flags icons to your users. The code argument receives the 2 letter country code. Use width, height and quality arguments to change the output settings. Country codes follow the [ISO 3166-1](https:\/\/en.wikipedia.org\/wiki\/ISO_3166-1) standard.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n",
+ "description": "You can use this endpoint to show different country flags icons to your users. The code argument receives the 2 letter country code. Use width, height and quality arguments to change the output settings. Country codes follow the [ISO 3166-1](https:\/\/en.wikipedia.org\/wiki\/ISO_3166-1) standard.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n",
"responses": {
"200": {
"description": "Image"
@@ -4185,7 +4185,7 @@
"tags": [
"avatars"
],
- "description": "Use this endpoint to fetch a remote image URL and crop it to any image size you want. This endpoint is very useful if you need to crop and display remote images in your app or in case you want to make sure a 3rd party image is properly served using a TLS protocol.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 400x400px.\n\nThis endpoint does not follow HTTP redirects.",
+ "description": "Use this endpoint to fetch a remote image URL and crop it to any image size you want. This endpoint is very useful if you need to crop and display remote images in your app or in case you want to make sure a 3rd party image is properly served using a TLS protocol.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 400x400px.\r\n\r\nThis endpoint does not follow HTTP redirects.",
"responses": {
"200": {
"description": "Image"
@@ -4269,7 +4269,7 @@
"tags": [
"avatars"
],
- "description": "Use this endpoint to show your user initials avatar icon on your website or app. By default, this route will try to print your logged-in user name or email initials. You can also overwrite the user name if you pass the 'name' parameter. If no name is given and no user is logged, an empty avatar will be returned.\n\nYou can use the color and background params to change the avatar colors. By default, a random theme will be selected. The random theme will persist for the user's initials when reloading the same theme will always return for the same initials.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n",
+ "description": "Use this endpoint to show your user initials avatar icon on your website or app. By default, this route will try to print your logged-in user name or email initials. You can also overwrite the user name if you pass the 'name' parameter. If no name is given and no user is logged, an empty avatar will be returned.\r\n\r\nYou can use the color and background params to change the avatar colors. By default, a random theme will be selected. The random theme will persist for the user's initials when reloading the same theme will always return for the same initials.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n",
"responses": {
"200": {
"description": "Image"
@@ -4363,7 +4363,7 @@
"tags": [
"avatars"
],
- "description": "Converts a given plain text to a QR code image. You can use the query parameters to change the size and style of the resulting image.\n",
+ "description": "Converts a given plain text to a QR code image. You can use the query parameters to change the size and style of the resulting image.\r\n",
"responses": {
"200": {
"description": "Image"
@@ -4452,7 +4452,7 @@
},
"\/console\/assistant": {
"post": {
- "summary": "Ask Query",
+ "summary": "Ask query",
"operationId": "assistantChat",
"tags": [
"assistant"
@@ -4644,7 +4644,7 @@
"tags": [
"databases"
],
- "description": "Create a new Database.\n",
+ "description": "Create a new Database.\r\n",
"responses": {
"201": {
"description": "Database",
@@ -5523,7 +5523,7 @@
"tags": [
"databases"
],
- "description": "Create a boolean attribute.\n",
+ "description": "Create a boolean attribute.\r\n",
"responses": {
"202": {
"description": "AttributeBoolean",
@@ -5965,7 +5965,7 @@
"tags": [
"databases"
],
- "description": "Create an email attribute.\n",
+ "description": "Create an email attribute.\r\n",
"responses": {
"202": {
"description": "AttributeEmail",
@@ -6073,7 +6073,7 @@
"tags": [
"databases"
],
- "description": "Update an email attribute. Changing the `default` value will not update already existing documents.\n",
+ "description": "Update an email attribute. Changing the `default` value will not update already existing documents.\r\n",
"responses": {
"200": {
"description": "AttributeEmail",
@@ -6186,7 +6186,7 @@
"tags": [
"databases"
],
- "description": "Create an enumeration attribute. The `elements` param acts as a white-list of accepted values for this attribute. \n",
+ "description": "Create an enumeration attribute. The `elements` param acts as a white-list of accepted values for this attribute. \r\n",
"responses": {
"202": {
"description": "AttributeEnum",
@@ -6303,7 +6303,7 @@
"tags": [
"databases"
],
- "description": "Update an enum attribute. Changing the `default` value will not update already existing documents.\n",
+ "description": "Update an enum attribute. Changing the `default` value will not update already existing documents.\r\n",
"responses": {
"200": {
"description": "AttributeEnum",
@@ -6425,7 +6425,7 @@
"tags": [
"databases"
],
- "description": "Create a float attribute. Optionally, minimum and maximum values can be provided.\n",
+ "description": "Create a float attribute. Optionally, minimum and maximum values can be provided.\r\n",
"responses": {
"202": {
"description": "AttributeFloat",
@@ -6543,7 +6543,7 @@
"tags": [
"databases"
],
- "description": "Update a float attribute. Changing the `default` value will not update already existing documents.\n",
+ "description": "Update a float attribute. Changing the `default` value will not update already existing documents.\r\n",
"responses": {
"200": {
"description": "AttributeFloat",
@@ -6668,7 +6668,7 @@
"tags": [
"databases"
],
- "description": "Create an integer attribute. Optionally, minimum and maximum values can be provided.\n",
+ "description": "Create an integer attribute. Optionally, minimum and maximum values can be provided.\r\n",
"responses": {
"202": {
"description": "AttributeInteger",
@@ -6786,7 +6786,7 @@
"tags": [
"databases"
],
- "description": "Update an integer attribute. Changing the `default` value will not update already existing documents.\n",
+ "description": "Update an integer attribute. Changing the `default` value will not update already existing documents.\r\n",
"responses": {
"200": {
"description": "AttributeInteger",
@@ -6911,7 +6911,7 @@
"tags": [
"databases"
],
- "description": "Create IP address attribute.\n",
+ "description": "Create IP address attribute.\r\n",
"responses": {
"202": {
"description": "AttributeIP",
@@ -7019,7 +7019,7 @@
"tags": [
"databases"
],
- "description": "Update an ip attribute. Changing the `default` value will not update already existing documents.\n",
+ "description": "Update an ip attribute. Changing the `default` value will not update already existing documents.\r\n",
"responses": {
"200": {
"description": "AttributeIP",
@@ -7132,7 +7132,7 @@
"tags": [
"databases"
],
- "description": "Create relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\n",
+ "description": "Create relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\r\n",
"responses": {
"202": {
"description": "AttributeRelationship",
@@ -7265,7 +7265,7 @@
"tags": [
"databases"
],
- "description": "Create a string attribute.\n",
+ "description": "Create a string attribute.\r\n",
"responses": {
"202": {
"description": "AttributeString",
@@ -7384,7 +7384,7 @@
"tags": [
"databases"
],
- "description": "Update a string attribute. Changing the `default` value will not update already existing documents.\n",
+ "description": "Update a string attribute. Changing the `default` value will not update already existing documents.\r\n",
"responses": {
"200": {
"description": "AttributeString",
@@ -7502,7 +7502,7 @@
"tags": [
"databases"
],
- "description": "Create a URL attribute.\n",
+ "description": "Create a URL attribute.\r\n",
"responses": {
"202": {
"description": "AttributeURL",
@@ -7610,7 +7610,7 @@
"tags": [
"databases"
],
- "description": "Update an url attribute. Changing the `default` value will not update already existing documents.\n",
+ "description": "Update an url attribute. Changing the `default` value will not update already existing documents.\r\n",
"responses": {
"200": {
"description": "AttributeURL",
@@ -7909,7 +7909,7 @@
"tags": [
"databases"
],
- "description": "Update relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\n",
+ "description": "Update relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\r\n",
"responses": {
"200": {
"description": "AttributeRelationship",
@@ -8678,7 +8678,7 @@
"tags": [
"databases"
],
- "description": "Creates an index on the attributes listed. Your index should include all the attributes you will query in a single request.\nAttributes can be `key`, `fulltext`, and `unique`.",
+ "description": "Creates an index on the attributes listed. Your index should include all the attributes you will query in a single request.\r\nAttributes can be `key`, `fulltext`, and `unique`.",
"responses": {
"202": {
"description": "Index",
@@ -9658,7 +9658,7 @@
"tags": [
"functions"
],
- "description": "List allowed function specifications for this instance.\n",
+ "description": "List allowed function specifications for this instance.\r\n",
"responses": {
"200": {
"description": "Specifications List",
@@ -10373,7 +10373,7 @@
"tags": [
"functions"
],
- "description": "Create a new function code deployment. Use this endpoint to upload a new version of your code function. To execute your newly uploaded code, you'll need to update the function's deployment to use your new deployment UID.\n\nThis endpoint accepts a tar.gz file compressed with your code. Make sure to include any dependencies your code has within the compressed file. You can learn more about code packaging in the [Appwrite Cloud Functions tutorial](https:\/\/appwrite.io\/docs\/functions).\n\nUse the \"command\" param to set the entrypoint used to execute your code.",
+ "description": "Create a new function code deployment. Use this endpoint to upload a new version of your code function. To execute your newly uploaded code, you'll need to update the function's deployment to use your new deployment UID.\r\n\r\nThis endpoint accepts a tar.gz file compressed with your code. Make sure to include any dependencies your code has within the compressed file. You can learn more about code packaging in the [Appwrite Cloud Functions tutorial](https:\/\/appwrite.io\/docs\/functions).\r\n\r\nUse the \"command\" param to set the entrypoint used to execute your code.",
"responses": {
"202": {
"description": "Deployment",
@@ -11179,7 +11179,7 @@
"tags": [
"functions"
],
- "description": "Delete a function execution by its unique ID.\n",
+ "description": "Delete a function execution by its unique ID.\r\n",
"responses": {
"204": {
"description": "No content"
@@ -12453,7 +12453,7 @@
"tags": [
"health"
],
- "description": "Returns the amount of failed jobs in a given queue.\n",
+ "description": "Returns the amount of failed jobs in a given queue.\r\n",
"responses": {
"200": {
"description": "Health Queue",
@@ -13208,7 +13208,7 @@
"tags": [
"locale"
],
- "description": "Get the current user location based on IP. Returns an object with user country code, country name, continent name, continent code, ip address and suggested currency. You can use the locale header to get the data in a supported language.\n\n([IP Geolocation by DB-IP](https:\/\/db-ip.com))",
+ "description": "Get the current user location based on IP. Returns an object with user country code, country name, continent name, continent code, ip address and suggested currency. You can use the locale header to get the data in a supported language.\r\n\r\n([IP Geolocation by DB-IP](https:\/\/db-ip.com))",
"responses": {
"200": {
"description": "Locale",
@@ -13257,7 +13257,7 @@
},
"\/locale\/codes": {
"get": {
- "summary": "List Locale Codes",
+ "summary": "List locale codes",
"operationId": "localeListCodes",
"tags": [
"locale"
@@ -13864,7 +13864,7 @@
"tags": [
"messaging"
],
- "description": "Update an email message by its unique ID.\n",
+ "description": "Update an email message by its unique ID.\r\n",
"responses": {
"200": {
"description": "Message",
@@ -14169,7 +14169,7 @@
"tags": [
"messaging"
],
- "description": "Update a push notification by its unique ID.\n",
+ "description": "Update a push notification by its unique ID.\r\n",
"responses": {
"200": {
"description": "Message",
@@ -14439,7 +14439,7 @@
"tags": [
"messaging"
],
- "description": "Update an email message by its unique ID.\n",
+ "description": "Update an email message by its unique ID.\r\n",
"responses": {
"200": {
"description": "Message",
@@ -14553,7 +14553,7 @@
"tags": [
"messaging"
],
- "description": "Get a message by its unique ID.\n",
+ "description": "Get a message by its unique ID.\r\n",
"responses": {
"200": {
"description": "Message",
@@ -17029,7 +17029,7 @@
"tags": [
"messaging"
],
- "description": "Get a provider by its unique ID.\n",
+ "description": "Get a provider by its unique ID.\r\n",
"responses": {
"200": {
"description": "Provider",
@@ -17463,7 +17463,7 @@
"tags": [
"messaging"
],
- "description": "Get a topic by its unique ID.\n",
+ "description": "Get a topic by its unique ID.\r\n",
"responses": {
"200": {
"description": "Topic",
@@ -17525,7 +17525,7 @@
"tags": [
"messaging"
],
- "description": "Update a topic by its unique ID.\n",
+ "description": "Update a topic by its unique ID.\r\n",
"responses": {
"200": {
"description": "Topic",
@@ -17923,7 +17923,7 @@
"tags": [
"messaging"
],
- "description": "Get a subscriber by its unique ID.\n",
+ "description": "Get a subscriber by its unique ID.\r\n",
"responses": {
"200": {
"description": "Subscriber",
@@ -18060,7 +18060,7 @@
},
"\/migrations": {
"get": {
- "summary": "List Migrations",
+ "summary": "List migrations",
"operationId": "migrationsList",
"tags": [
"migrations"
@@ -18136,7 +18136,7 @@
},
"\/migrations\/appwrite": {
"post": {
- "summary": "Migrate Appwrite Data",
+ "summary": "Migrate Appwrite data",
"operationId": "migrationsCreateAppwriteMigration",
"tags": [
"migrations"
@@ -18226,7 +18226,7 @@
},
"\/migrations\/appwrite\/report": {
"get": {
- "summary": "Generate a report on Appwrite Data",
+ "summary": "Generate a report on Appwrite data",
"operationId": "migrationsGetAppwriteReport",
"tags": [
"migrations"
@@ -18321,7 +18321,7 @@
},
"\/migrations\/firebase": {
"post": {
- "summary": "Migrate Firebase Data (Service Account)",
+ "summary": "Migrate Firebase data (Service Account)",
"operationId": "migrationsCreateFirebaseMigration",
"tags": [
"migrations"
@@ -18399,7 +18399,7 @@
},
"\/migrations\/firebase\/deauthorize": {
"get": {
- "summary": "Revoke Appwrite's authorization to access Firebase Projects",
+ "summary": "Revoke Appwrite's authorization to access Firebase projects",
"operationId": "migrationsDeleteFirebaseAuth",
"tags": [
"migrations"
@@ -18442,7 +18442,7 @@
},
"\/migrations\/firebase\/oauth": {
"post": {
- "summary": "Migrate Firebase Data (OAuth)",
+ "summary": "Migrate Firebase data (OAuth)",
"operationId": "migrationsCreateFirebaseOAuthMigration",
"tags": [
"migrations"
@@ -18520,7 +18520,7 @@
},
"\/migrations\/firebase\/projects": {
"get": {
- "summary": "List Firebase Projects",
+ "summary": "List Firebase projects",
"operationId": "migrationsListFirebaseProjects",
"tags": [
"migrations"
@@ -18570,7 +18570,7 @@
},
"\/migrations\/firebase\/report": {
"get": {
- "summary": "Generate a report on Firebase Data",
+ "summary": "Generate a report on Firebase data",
"operationId": "migrationsGetFirebaseReport",
"tags": [
"migrations"
@@ -18644,7 +18644,7 @@
},
"\/migrations\/firebase\/report\/oauth": {
"get": {
- "summary": "Generate a report on Firebase Data using OAuth",
+ "summary": "Generate a report on Firebase data using OAuth",
"operationId": "migrationsGetFirebaseReportOAuth",
"tags": [
"migrations"
@@ -18718,7 +18718,7 @@
},
"\/migrations\/nhost": {
"post": {
- "summary": "Migrate NHost Data",
+ "summary": "Migrate NHost data",
"operationId": "migrationsCreateNHostMigration",
"tags": [
"migrations"
@@ -18966,7 +18966,7 @@
},
"\/migrations\/supabase": {
"post": {
- "summary": "Migrate Supabase Data",
+ "summary": "Migrate Supabase data",
"operationId": "migrationsCreateSupabaseMigration",
"tags": [
"migrations"
@@ -19199,7 +19199,7 @@
},
"\/migrations\/{migrationId}": {
"get": {
- "summary": "Get Migration",
+ "summary": "Get migration",
"operationId": "migrationsGet",
"tags": [
"migrations"
@@ -19259,7 +19259,7 @@
]
},
"patch": {
- "summary": "Retry Migration",
+ "summary": "Retry migration",
"operationId": "migrationsRetry",
"tags": [
"migrations"
@@ -19319,7 +19319,7 @@
]
},
"delete": {
- "summary": "Delete Migration",
+ "summary": "Delete migration",
"operationId": "migrationsDelete",
"tags": [
"migrations"
@@ -19464,7 +19464,7 @@
},
"\/project\/variables": {
"get": {
- "summary": "List Variables",
+ "summary": "List variables",
"operationId": "projectListVariables",
"tags": [
"project"
@@ -19512,7 +19512,7 @@
]
},
"post": {
- "summary": "Create Variable",
+ "summary": "Create variable",
"operationId": "projectCreateVariable",
"tags": [
"project"
@@ -19587,7 +19587,7 @@
},
"\/project\/variables\/{variableId}": {
"get": {
- "summary": "Get Variable",
+ "summary": "Get variable",
"operationId": "projectGetVariable",
"tags": [
"project"
@@ -19647,7 +19647,7 @@
]
},
"put": {
- "summary": "Update Variable",
+ "summary": "Update variable",
"operationId": "projectUpdateVariable",
"tags": [
"project"
@@ -19731,7 +19731,7 @@
}
},
"delete": {
- "summary": "Delete Variable",
+ "summary": "Delete variable",
"operationId": "projectDeleteVariable",
"tags": [
"project"
@@ -21282,7 +21282,7 @@
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
- "scope": "projects.read",
+ "scope": "keys.read",
"platforms": [
"console"
],
@@ -21342,7 +21342,7 @@
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
- "scope": "projects.write",
+ "scope": "keys.write",
"platforms": [
"console"
],
@@ -21437,7 +21437,7 @@
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
- "scope": "projects.read",
+ "scope": "keys.read",
"platforms": [
"console"
],
@@ -21507,7 +21507,7 @@
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
- "scope": "projects.write",
+ "scope": "keys.write",
"platforms": [
"console"
],
@@ -21603,7 +21603,7 @@
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
- "scope": "projects.write",
+ "scope": "keys.write",
"platforms": [
"console"
],
@@ -21814,7 +21814,7 @@
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
- "scope": "projects.read",
+ "scope": "platforms.read",
"platforms": [
"console"
],
@@ -21874,7 +21874,7 @@
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
- "scope": "projects.write",
+ "scope": "platforms.write",
"platforms": [
"console"
],
@@ -21995,7 +21995,7 @@
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
- "scope": "projects.read",
+ "scope": "platforms.read",
"platforms": [
"console"
],
@@ -22065,7 +22065,7 @@
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
- "scope": "projects.write",
+ "scope": "platforms.write",
"platforms": [
"console"
],
@@ -22162,7 +22162,7 @@
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
- "scope": "projects.write",
+ "scope": "platforms.write",
"platforms": [
"console"
],
@@ -22620,7 +22620,8 @@
"description": "Does SMTP server use secure connection",
"x-example": "tls",
"enum": [
- "tls"
+ "tls",
+ "ssl"
],
"x-enum-name": "SMTPSecure",
"x-enum-keys": []
@@ -24640,7 +24641,7 @@
},
"\/proxy\/rules": {
"get": {
- "summary": "List Rules",
+ "summary": "List rules",
"operationId": "proxyListRules",
"tags": [
"proxy"
@@ -24714,7 +24715,7 @@
]
},
"post": {
- "summary": "Create Rule",
+ "summary": "Create rule",
"operationId": "proxyCreateRule",
"tags": [
"proxy"
@@ -24800,7 +24801,7 @@
},
"\/proxy\/rules\/{ruleId}": {
"get": {
- "summary": "Get Rule",
+ "summary": "Get rule",
"operationId": "proxyGetRule",
"tags": [
"proxy"
@@ -24860,7 +24861,7 @@
]
},
"delete": {
- "summary": "Delete Rule",
+ "summary": "Delete rule",
"operationId": "proxyDeleteRule",
"tags": [
"proxy"
@@ -24915,7 +24916,7 @@
},
"\/proxy\/rules\/{ruleId}\/verification": {
"patch": {
- "summary": "Update Rule Verification Status",
+ "summary": "Update rule verification status",
"operationId": "proxyUpdateRuleVerification",
"tags": [
"proxy"
@@ -25524,7 +25525,7 @@
"tags": [
"storage"
],
- "description": "Create a new file. Before using this route, you should create a new bucket resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/storage#storageCreateBucket) API or directly from your Appwrite console.\n\nLarger files should be uploaded using multiple requests with the [content-range](https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Content-Range) header to send a partial request with a maximum supported chunk of `5MB`. The `content-range` header values should always be in bytes.\n\nWhen the first request is sent, the server will return the **File** object, and the subsequent part request must include the file's **id** in `x-appwrite-id` header to allow the server to know that the partial upload is for the existing file and not for a new one.\n\nIf you're creating a new file using one of the Appwrite SDKs, all the chunking logic will be managed by the SDK internally.\n",
+ "description": "Create a new file. Before using this route, you should create a new bucket resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/storage#storageCreateBucket) API or directly from your Appwrite console.\r\n\r\nLarger files should be uploaded using multiple requests with the [content-range](https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Content-Range) header to send a partial request with a maximum supported chunk of `5MB`. The `content-range` header values should always be in bytes.\r\n\r\nWhen the first request is sent, the server will return the **File** object, and the subsequent part request must include the file's **id** in `x-appwrite-id` header to allow the server to know that the partial upload is for the existing file and not for a new one.\r\n\r\nIf you're creating a new file using one of the Appwrite SDKs, all the chunking logic will be managed by the SDK internally.\r\n",
"responses": {
"201": {
"description": "File",
@@ -25791,7 +25792,7 @@
}
},
"delete": {
- "summary": "Delete File",
+ "summary": "Delete file",
"operationId": "storageDeleteFile",
"tags": [
"storage"
@@ -26133,7 +26134,8 @@
"jpeg",
"gif",
"png",
- "webp"
+ "webp",
+ "avif"
],
"x-enum-name": "ImageFormat",
"x-enum-keys": [],
@@ -26912,7 +26914,7 @@
"tags": [
"teams"
],
- "description": "Invite a new member to join your team. Provide an ID for existing users, or invite unregistered users using an email or phone number. If initiated from a Client SDK, Appwrite will send an email or sms with a link to join the team to the invited user, and an account will be created for them if one doesn't exist. If initiated from a Server SDK, the new member will be added automatically to the team.\n\nYou only need to provide one of a user ID, email, or phone number. Appwrite will prioritize accepting the user ID > email > phone number if you provide more than one of these parameters.\n\nUse the `url` parameter to redirect the user from the invitation email to your app. After the user is redirected, use the [Update Team Membership Status](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/teams#updateMembershipStatus) endpoint to allow the user to accept the invitation to the team. \n\nPlease note that to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) Appwrite will accept the only redirect URLs under the domains you have added as a platform on the Appwrite Console.\n",
+ "description": "Invite a new member to join your team. Provide an ID for existing users, or invite unregistered users using an email or phone number. If initiated from a Client SDK, Appwrite will send an email or sms with a link to join the team to the invited user, and an account will be created for them if one doesn't exist. If initiated from a Server SDK, the new member will be added automatically to the team.\r\n\r\nYou only need to provide one of a user ID, email, or phone number. Appwrite will prioritize accepting the user ID > email > phone number if you provide more than one of these parameters.\r\n\r\nUse the `url` parameter to redirect the user from the invitation email to your app. After the user is redirected, use the [Update Team Membership Status](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/teams#updateMembershipStatus) endpoint to allow the user to accept the invitation to the team. \r\n\r\nPlease note that to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) Appwrite will accept the only redirect URLs under the domains you have added as a platform on the Appwrite Console.\r\n",
"responses": {
"201": {
"description": "Membership",
@@ -27099,7 +27101,7 @@
"tags": [
"teams"
],
- "description": "Modify the roles of a team member. Only team members with the owner role have access to this endpoint. Learn more about [roles and permissions](https:\/\/appwrite.io\/docs\/permissions).\n",
+ "description": "Modify the roles of a team member. Only team members with the owner role have access to this endpoint. Learn more about [roles and permissions](https:\/\/appwrite.io\/docs\/permissions).\r\n",
"responses": {
"200": {
"description": "Membership",
@@ -27264,7 +27266,7 @@
"tags": [
"teams"
],
- "description": "Use this endpoint to allow a user to accept an invitation to join a team after being redirected back to your app from the invitation email received by the user.\n\nIf the request is successful, a session for the user is automatically created.\n",
+ "description": "Use this endpoint to allow a user to accept an invitation to join a team after being redirected back to your app from the invitation email received by the user.\r\n\r\nIf the request is successful, a session for the user is automatically created.\r\n",
"responses": {
"200": {
"description": "Membership",
@@ -27842,7 +27844,7 @@
},
"\/users\/identities": {
"get": {
- "summary": "List Identities",
+ "summary": "List identities",
"operationId": "usersListIdentities",
"tags": [
"users"
@@ -28840,7 +28842,7 @@
"tags": [
"users"
],
- "description": "Update the user labels by its unique ID. \n\nLabels can be used to grant access to resources. While teams are a way for user's to share access to a resource, labels can be defined by the developer to grant access without an invitation. See the [Permissions docs](https:\/\/appwrite.io\/docs\/permissions) for more info.",
+ "description": "Update the user labels by its unique ID. \r\n\r\nLabels can be used to grant access to resources. While teams are a way for user's to share access to a resource, labels can be defined by the developer to grant access without an invitation. See the [Permissions docs](https:\/\/appwrite.io\/docs\/permissions) for more info.",
"responses": {
"200": {
"description": "User",
@@ -29141,7 +29143,7 @@
},
"\/users\/{userId}\/mfa\/authenticators\/{type}": {
"delete": {
- "summary": "Delete Authenticator",
+ "summary": "Delete authenticator",
"operationId": "usersDeleteMfaAuthenticator",
"tags": [
"users"
@@ -29219,7 +29221,7 @@
},
"\/users\/{userId}\/mfa\/factors": {
"get": {
- "summary": "List Factors",
+ "summary": "List factors",
"operationId": "usersListMfaFactors",
"tags": [
"users"
@@ -29282,7 +29284,7 @@
},
"\/users\/{userId}\/mfa\/recovery-codes": {
"get": {
- "summary": "Get MFA Recovery Codes",
+ "summary": "Get MFA recovery codes",
"operationId": "usersGetMfaRecoveryCodes",
"tags": [
"users"
@@ -29343,7 +29345,7 @@
]
},
"put": {
- "summary": "Regenerate MFA Recovery Codes",
+ "summary": "Regenerate MFA recovery codes",
"operationId": "usersUpdateMfaRecoveryCodes",
"tags": [
"users"
@@ -29404,7 +29406,7 @@
]
},
"patch": {
- "summary": "Create MFA Recovery Codes",
+ "summary": "Create MFA recovery codes",
"operationId": "usersCreateMfaRecoveryCodes",
"tags": [
"users"
@@ -29922,7 +29924,7 @@
"tags": [
"users"
],
- "description": "Creates a session for a user. Returns an immediately usable session object.\n\nIf you want to generate a token for a custom authentication flow, use the [POST \/users\/{userId}\/tokens](https:\/\/appwrite.io\/docs\/server\/users#createToken) endpoint.",
+ "description": "Creates a session for a user. Returns an immediately usable session object.\r\n\r\nIf you want to generate a token for a custom authentication flow, use the [POST \/users\/{userId}\/tokens](https:\/\/appwrite.io\/docs\/server\/users#createToken) endpoint.",
"responses": {
"201": {
"description": "Session",
@@ -30182,7 +30184,7 @@
},
"\/users\/{userId}\/targets": {
"get": {
- "summary": "List User Targets",
+ "summary": "List user targets",
"operationId": "usersListTargets",
"tags": [
"users"
@@ -30257,7 +30259,7 @@
]
},
"post": {
- "summary": "Create User Target",
+ "summary": "Create user target",
"operationId": "usersCreateTarget",
"tags": [
"users"
@@ -30369,7 +30371,7 @@
},
"\/users\/{userId}\/targets\/{targetId}": {
"get": {
- "summary": "Get User Target",
+ "summary": "Get user target",
"operationId": "usersGetTarget",
"tags": [
"users"
@@ -30441,7 +30443,7 @@
]
},
"patch": {
- "summary": "Update User target",
+ "summary": "Update user target",
"operationId": "usersUpdateTarget",
"tags": [
"users"
@@ -30611,7 +30613,7 @@
"tags": [
"users"
],
- "description": "Returns a token with a secret key for creating a session. Use the user ID and secret and submit a request to the [PUT \/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process.\n",
+ "description": "Returns a token with a secret key for creating a session. Use the user ID and secret and submit a request to the [PUT \/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process.\r\n",
"responses": {
"201": {
"description": "Token",
@@ -30854,7 +30856,7 @@
},
"\/vcs\/github\/installations\/{installationId}\/providerRepositories": {
"get": {
- "summary": "List Repositories",
+ "summary": "List repositories",
"operationId": "vcsListRepositories",
"tags": [
"vcs"
@@ -31084,7 +31086,7 @@
},
"\/vcs\/github\/installations\/{installationId}\/providerRepositories\/{providerRepositoryId}\/branches": {
"get": {
- "summary": "List Repository Branches",
+ "summary": "List repository branches",
"operationId": "vcsListRepositoryBranches",
"tags": [
"vcs"
@@ -31547,7 +31549,7 @@
]
},
"delete": {
- "summary": "Delete Installation",
+ "summary": "Delete installation",
"operationId": "vcsDeleteInstallation",
"tags": [
"vcs"
@@ -32946,6 +32948,16 @@
"x-example": false,
"nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"size": {
"type": "integer",
"description": "Attribute size.",
@@ -32965,6 +32977,8 @@
"status",
"error",
"required",
+ "$createdAt",
+ "$updatedAt",
"size"
]
},
@@ -33003,6 +33017,16 @@
"x-example": false,
"nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"min": {
"type": "integer",
"description": "Minimum value to enforce for new documents.",
@@ -33030,7 +33054,9 @@
"type",
"status",
"error",
- "required"
+ "required",
+ "$createdAt",
+ "$updatedAt"
]
},
"attributeFloat": {
@@ -33068,6 +33094,16 @@
"x-example": false,
"nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"min": {
"type": "number",
"description": "Minimum value to enforce for new documents.",
@@ -33095,7 +33131,9 @@
"type",
"status",
"error",
- "required"
+ "required",
+ "$createdAt",
+ "$updatedAt"
]
},
"attributeBoolean": {
@@ -33133,6 +33171,16 @@
"x-example": false,
"nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"default": {
"type": "boolean",
"description": "Default value for attribute when not provided. Cannot be set when attribute is required.",
@@ -33145,7 +33193,9 @@
"type",
"status",
"error",
- "required"
+ "required",
+ "$createdAt",
+ "$updatedAt"
]
},
"attributeEmail": {
@@ -33183,6 +33233,16 @@
"x-example": false,
"nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"format": {
"type": "string",
"description": "String format.",
@@ -33201,6 +33261,8 @@
"status",
"error",
"required",
+ "$createdAt",
+ "$updatedAt",
"format"
]
},
@@ -33239,6 +33301,16 @@
"x-example": false,
"nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"elements": {
"type": "array",
"description": "Array of elements in enumerated type.",
@@ -33265,6 +33337,8 @@
"status",
"error",
"required",
+ "$createdAt",
+ "$updatedAt",
"elements",
"format"
]
@@ -33304,6 +33378,16 @@
"x-example": false,
"nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"format": {
"type": "string",
"description": "String format.",
@@ -33322,6 +33406,8 @@
"status",
"error",
"required",
+ "$createdAt",
+ "$updatedAt",
"format"
]
},
@@ -33360,6 +33446,16 @@
"x-example": false,
"nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"format": {
"type": "string",
"description": "String format.",
@@ -33378,6 +33474,8 @@
"status",
"error",
"required",
+ "$createdAt",
+ "$updatedAt",
"format"
]
},
@@ -33416,6 +33514,16 @@
"x-example": false,
"nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"format": {
"type": "string",
"description": "ISO 8601 format.",
@@ -33434,6 +33542,8 @@
"status",
"error",
"required",
+ "$createdAt",
+ "$updatedAt",
"format"
]
},
@@ -33472,6 +33582,16 @@
"x-example": false,
"nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"relatedCollection": {
"type": "string",
"description": "The ID of the related collection.",
@@ -33509,6 +33629,8 @@
"status",
"error",
"required",
+ "$createdAt",
+ "$updatedAt",
"relatedCollection",
"relationType",
"twoWay",
@@ -33557,6 +33679,16 @@
},
"x-example": [],
"nullable": true
+ },
+ "$createdAt": {
+ "type": "string",
+ "description": "Index creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Index update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
}
},
"required": [
@@ -33564,7 +33696,9 @@
"type",
"status",
"error",
- "attributes"
+ "attributes",
+ "$createdAt",
+ "$updatedAt"
]
},
"document": {
@@ -35589,7 +35723,7 @@
"responseBody": {
"type": "string",
"description": "HTTP response body. This will return empty unless execution is created as synchronous.",
- "x-example": "Developers are awesome."
+ "x-example": ""
},
"responseHeaders": {
"type": "array",
@@ -36747,6 +36881,12 @@
"x-example": 0,
"format": "int32"
},
+ "storageTotal": {
+ "type": "integer",
+ "description": "Total aggregated number of total databases storage in bytes.",
+ "x-example": 0,
+ "format": "int32"
+ },
"databases": {
"type": "array",
"description": "Aggregated number of databases per period.",
@@ -36770,6 +36910,14 @@
"$ref": "#\/components\/schemas\/metric"
},
"x-example": []
+ },
+ "storage": {
+ "type": "array",
+ "description": "An array of the aggregated number of databases storage in bytes per period.",
+ "items": {
+ "$ref": "#\/components\/schemas\/metric"
+ },
+ "x-example": []
}
},
"required": [
@@ -36777,9 +36925,11 @@
"databasesTotal",
"collectionsTotal",
"documentsTotal",
+ "storageTotal",
"databases",
"collections",
- "documents"
+ "documents",
+ "storage"
]
},
"usageDatabase": {
@@ -36803,6 +36953,12 @@
"x-example": 0,
"format": "int32"
},
+ "storageTotal": {
+ "type": "integer",
+ "description": "Total aggregated number of total storage used in bytes.",
+ "x-example": 0,
+ "format": "int32"
+ },
"collections": {
"type": "array",
"description": "Aggregated number of collections per period.",
@@ -36818,14 +36974,24 @@
"$ref": "#\/components\/schemas\/metric"
},
"x-example": []
+ },
+ "storage": {
+ "type": "array",
+ "description": "Aggregated storage used in bytes per period.",
+ "items": {
+ "$ref": "#\/components\/schemas\/metric"
+ },
+ "x-example": []
}
},
"required": [
"range",
"collectionsTotal",
"documentsTotal",
+ "storageTotal",
"collections",
- "documents"
+ "documents",
+ "storage"
]
},
"usageCollection": {
@@ -37366,6 +37532,12 @@
"x-example": 0,
"format": "int32"
},
+ "databasesStorageTotal": {
+ "type": "integer",
+ "description": "Total aggregated sum of databases storage size (in bytes).",
+ "x-example": 0,
+ "format": "int32"
+ },
"usersTotal": {
"type": "integer",
"description": "Total aggregated number of users.",
@@ -37462,6 +37634,14 @@
},
"x-example": []
},
+ "databasesStorageBreakdown": {
+ "type": "array",
+ "description": "An array of the aggregated breakdown of storage usage by databases.",
+ "items": {
+ "$ref": "#\/components\/schemas\/metricBreakdown"
+ },
+ "x-example": []
+ },
"executionsMbSecondsBreakdown": {
"type": "array",
"description": "Aggregated breakdown in totals of execution mbSeconds by functions.",
@@ -37491,6 +37671,7 @@
"executionsTotal",
"documentsTotal",
"databasesTotal",
+ "databasesStorageTotal",
"usersTotal",
"filesStorageTotal",
"functionsStorageTotal",
@@ -37505,6 +37686,7 @@
"executions",
"executionsBreakdown",
"bucketsBreakdown",
+ "databasesStorageBreakdown",
"executionsMbSecondsBreakdown",
"buildsMbSecondsBreakdown",
"functionsStorageBreakdown"
diff --git a/app/config/specs/open-api3-1.6.x-server.json b/app/config/specs/open-api3-1.6.x-server.json
index ef41688ca8..274eac9686 100644
--- a/app/config/specs/open-api3-1.6.x-server.json
+++ b/app/config/specs/open-api3-1.6.x-server.json
@@ -167,7 +167,7 @@
"tags": [
"account"
],
- "description": "Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. For security measures, user password is required to complete this request.\nThis endpoint can also be used to convert an anonymous account to a normal one, by passing an email address and a new password.\n",
+ "description": "Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. For security measures, user password is required to complete this request.\r\nThis endpoint can also be used to convert an anonymous account to a normal one, by passing an email address and a new password.\r\n",
"responses": {
"200": {
"description": "User",
@@ -241,7 +241,7 @@
},
"\/account\/identities": {
"get": {
- "summary": "List Identities",
+ "summary": "List identities",
"operationId": "accountListIdentities",
"tags": [
"account"
@@ -562,7 +562,7 @@
},
"\/account\/mfa\/authenticators\/{type}": {
"post": {
- "summary": "Create Authenticator",
+ "summary": "Create authenticator",
"operationId": "accountCreateMfaAuthenticator",
"tags": [
"account"
@@ -631,7 +631,7 @@
]
},
"put": {
- "summary": "Verify Authenticator",
+ "summary": "Verify authenticator",
"operationId": "accountUpdateMfaAuthenticator",
"tags": [
"account"
@@ -719,7 +719,7 @@
}
},
"delete": {
- "summary": "Delete Authenticator",
+ "summary": "Delete authenticator",
"operationId": "accountDeleteMfaAuthenticator",
"tags": [
"account"
@@ -783,7 +783,7 @@
},
"\/account\/mfa\/challenge": {
"post": {
- "summary": "Create MFA Challenge",
+ "summary": "Create MFA challenge",
"operationId": "accountCreateMfaChallenge",
"tags": [
"account"
@@ -859,7 +859,7 @@
}
},
"put": {
- "summary": "Create MFA Challenge (confirmation)",
+ "summary": "Create MFA challenge (confirmation)",
"operationId": "accountUpdateMfaChallenge",
"tags": [
"account"
@@ -938,7 +938,7 @@
},
"\/account\/mfa\/factors": {
"get": {
- "summary": "List Factors",
+ "summary": "List factors",
"operationId": "accountListMfaFactors",
"tags": [
"account"
@@ -992,7 +992,7 @@
},
"\/account\/mfa\/recovery-codes": {
"get": {
- "summary": "Get MFA Recovery Codes",
+ "summary": "Get MFA recovery codes",
"operationId": "accountGetMfaRecoveryCodes",
"tags": [
"account"
@@ -1044,7 +1044,7 @@
]
},
"post": {
- "summary": "Create MFA Recovery Codes",
+ "summary": "Create MFA recovery codes",
"operationId": "accountCreateMfaRecoveryCodes",
"tags": [
"account"
@@ -1096,7 +1096,7 @@
]
},
"patch": {
- "summary": "Regenerate MFA Recovery Codes",
+ "summary": "Regenerate MFA recovery codes",
"operationId": "accountUpdateMfaRecoveryCodes",
"tags": [
"account"
@@ -1590,7 +1590,7 @@
"tags": [
"account"
],
- "description": "Use this endpoint to complete the user account password reset. Both the **userId** and **secret** arguments will be passed as query parameters to the redirect URL you have provided when sending your request to the [POST \/account\/recovery](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createRecovery) endpoint.\n\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.",
+ "description": "Use this endpoint to complete the user account password reset. Both the **userId** and **secret** arguments will be passed as query parameters to the redirect URL you have provided when sending your request to the [POST \/account\/recovery](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createRecovery) endpoint.\r\n\r\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.",
"responses": {
"200": {
"description": "Token",
@@ -1825,7 +1825,7 @@
"tags": [
"account"
],
- "description": "Allow the user to login into their account by providing a valid email and password combination. This route will create a new session for the user.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
+ "description": "Allow the user to login into their account by providing a valid email and password combination. This route will create a new session for the user.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
"responses": {
"201": {
"description": "Session",
@@ -2370,7 +2370,7 @@
"tags": [
"account"
],
- "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's email is valid for 15 minutes.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
+ "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's email is valid for 15 minutes.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
"responses": {
"201": {
"description": "Token",
@@ -2451,7 +2451,7 @@
"tags": [
"account"
],
- "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour. If you are on a mobile device you can leave the URL parameter empty, so that the login completion will be handled by your Appwrite instance by default.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\n",
+ "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour. If you are on a mobile device you can leave the URL parameter empty, so that the login completion will be handled by your Appwrite instance by default.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\r\n",
"responses": {
"201": {
"description": "Token",
@@ -2540,7 +2540,7 @@
"tags": [
"account"
],
- "description": "Allow the user to login to their account using the OAuth2 provider of their choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed. \n\nIf authentication succeeds, `userId` and `secret` of a token will be appended to the success URL as query parameters. These can be used to create a new session using the [Create session](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
+ "description": "Allow the user to login to their account using the OAuth2 provider of their choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed. \r\n\r\nIf authentication succeeds, `userId` and `secret` of a token will be appended to the success URL as query parameters. These can be used to create a new session using the [Create session](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
"responses": {
"301": {
"description": "File"
@@ -2676,7 +2676,7 @@
"tags": [
"account"
],
- "description": "Sends the user an SMS with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's phone is valid for 15 minutes.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
+ "description": "Sends the user an SMS with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's phone is valid for 15 minutes.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
"responses": {
"201": {
"description": "Token",
@@ -2755,7 +2755,7 @@
"tags": [
"account"
],
- "description": "Use this endpoint to send a verification message to your user email address to confirm they are the valid owners of that address. Both the **userId** and **secret** arguments will be passed as query parameters to the URL you have provided to be attached to the verification email. The provided URL should redirect the user back to your app and allow you to complete the verification process by verifying both the **userId** and **secret** parameters. Learn more about how to [complete the verification process](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\n\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.\n",
+ "description": "Use this endpoint to send a verification message to your user email address to confirm they are the valid owners of that address. Both the **userId** and **secret** arguments will be passed as query parameters to the URL you have provided to be attached to the verification email. The provided URL should redirect the user back to your app and allow you to complete the verification process by verifying both the **userId** and **secret** parameters. Learn more about how to [complete the verification process](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\r\n\r\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.\r\n",
"responses": {
"201": {
"description": "Token",
@@ -3039,7 +3039,7 @@
"tags": [
"avatars"
],
- "description": "You can use this endpoint to show different browser icons to your users. The code argument receives the browser code as it appears in your user [GET \/account\/sessions](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#getSessions) endpoint. Use width, height and quality arguments to change the output settings.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.",
+ "description": "You can use this endpoint to show different browser icons to your users. The code argument receives the browser code as it appears in your user [GET \/account\/sessions](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#getSessions) endpoint. Use width, height and quality arguments to change the output settings.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.",
"responses": {
"200": {
"description": "Image"
@@ -3169,7 +3169,7 @@
"tags": [
"avatars"
],
- "description": "The credit card endpoint will return you the icon of the credit card provider you need. Use width, height and quality arguments to change the output settings.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n",
+ "description": "The credit card endpoint will return you the icon of the credit card provider you need. Use width, height and quality arguments to change the output settings.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n",
"responses": {
"200": {
"description": "Image"
@@ -3303,7 +3303,7 @@
"tags": [
"avatars"
],
- "description": "Use this endpoint to fetch the favorite icon (AKA favicon) of any remote website URL.\n\nThis endpoint does not follow HTTP redirects.",
+ "description": "Use this endpoint to fetch the favorite icon (AKA favicon) of any remote website URL.\r\n\r\nThis endpoint does not follow HTTP redirects.",
"responses": {
"200": {
"description": "Image"
@@ -3365,7 +3365,7 @@
"tags": [
"avatars"
],
- "description": "You can use this endpoint to show different country flags icons to your users. The code argument receives the 2 letter country code. Use width, height and quality arguments to change the output settings. Country codes follow the [ISO 3166-1](https:\/\/en.wikipedia.org\/wiki\/ISO_3166-1) standard.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n",
+ "description": "You can use this endpoint to show different country flags icons to your users. The code argument receives the 2 letter country code. Use width, height and quality arguments to change the output settings. Country codes follow the [ISO 3166-1](https:\/\/en.wikipedia.org\/wiki\/ISO_3166-1) standard.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n",
"responses": {
"200": {
"description": "Image"
@@ -3857,7 +3857,7 @@
"tags": [
"avatars"
],
- "description": "Use this endpoint to fetch a remote image URL and crop it to any image size you want. This endpoint is very useful if you need to crop and display remote images in your app or in case you want to make sure a 3rd party image is properly served using a TLS protocol.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 400x400px.\n\nThis endpoint does not follow HTTP redirects.",
+ "description": "Use this endpoint to fetch a remote image URL and crop it to any image size you want. This endpoint is very useful if you need to crop and display remote images in your app or in case you want to make sure a 3rd party image is properly served using a TLS protocol.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 400x400px.\r\n\r\nThis endpoint does not follow HTTP redirects.",
"responses": {
"200": {
"description": "Image"
@@ -3943,7 +3943,7 @@
"tags": [
"avatars"
],
- "description": "Use this endpoint to show your user initials avatar icon on your website or app. By default, this route will try to print your logged-in user name or email initials. You can also overwrite the user name if you pass the 'name' parameter. If no name is given and no user is logged, an empty avatar will be returned.\n\nYou can use the color and background params to change the avatar colors. By default, a random theme will be selected. The random theme will persist for the user's initials when reloading the same theme will always return for the same initials.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n",
+ "description": "Use this endpoint to show your user initials avatar icon on your website or app. By default, this route will try to print your logged-in user name or email initials. You can also overwrite the user name if you pass the 'name' parameter. If no name is given and no user is logged, an empty avatar will be returned.\r\n\r\nYou can use the color and background params to change the avatar colors. By default, a random theme will be selected. The random theme will persist for the user's initials when reloading the same theme will always return for the same initials.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n",
"responses": {
"200": {
"description": "Image"
@@ -4039,7 +4039,7 @@
"tags": [
"avatars"
],
- "description": "Converts a given plain text to a QR code image. You can use the query parameters to change the size and style of the resulting image.\n",
+ "description": "Converts a given plain text to a QR code image. You can use the query parameters to change the size and style of the resulting image.\r\n",
"responses": {
"200": {
"description": "Image"
@@ -4211,7 +4211,7 @@
"tags": [
"databases"
],
- "description": "Create a new Database.\n",
+ "description": "Create a new Database.\r\n",
"responses": {
"201": {
"description": "Database",
@@ -5026,7 +5026,7 @@
"tags": [
"databases"
],
- "description": "Create a boolean attribute.\n",
+ "description": "Create a boolean attribute.\r\n",
"responses": {
"202": {
"description": "AttributeBoolean",
@@ -5472,7 +5472,7 @@
"tags": [
"databases"
],
- "description": "Create an email attribute.\n",
+ "description": "Create an email attribute.\r\n",
"responses": {
"202": {
"description": "AttributeEmail",
@@ -5581,7 +5581,7 @@
"tags": [
"databases"
],
- "description": "Update an email attribute. Changing the `default` value will not update already existing documents.\n",
+ "description": "Update an email attribute. Changing the `default` value will not update already existing documents.\r\n",
"responses": {
"200": {
"description": "AttributeEmail",
@@ -5695,7 +5695,7 @@
"tags": [
"databases"
],
- "description": "Create an enumeration attribute. The `elements` param acts as a white-list of accepted values for this attribute. \n",
+ "description": "Create an enumeration attribute. The `elements` param acts as a white-list of accepted values for this attribute. \r\n",
"responses": {
"202": {
"description": "AttributeEnum",
@@ -5813,7 +5813,7 @@
"tags": [
"databases"
],
- "description": "Update an enum attribute. Changing the `default` value will not update already existing documents.\n",
+ "description": "Update an enum attribute. Changing the `default` value will not update already existing documents.\r\n",
"responses": {
"200": {
"description": "AttributeEnum",
@@ -5936,7 +5936,7 @@
"tags": [
"databases"
],
- "description": "Create a float attribute. Optionally, minimum and maximum values can be provided.\n",
+ "description": "Create a float attribute. Optionally, minimum and maximum values can be provided.\r\n",
"responses": {
"202": {
"description": "AttributeFloat",
@@ -6055,7 +6055,7 @@
"tags": [
"databases"
],
- "description": "Update a float attribute. Changing the `default` value will not update already existing documents.\n",
+ "description": "Update a float attribute. Changing the `default` value will not update already existing documents.\r\n",
"responses": {
"200": {
"description": "AttributeFloat",
@@ -6181,7 +6181,7 @@
"tags": [
"databases"
],
- "description": "Create an integer attribute. Optionally, minimum and maximum values can be provided.\n",
+ "description": "Create an integer attribute. Optionally, minimum and maximum values can be provided.\r\n",
"responses": {
"202": {
"description": "AttributeInteger",
@@ -6300,7 +6300,7 @@
"tags": [
"databases"
],
- "description": "Update an integer attribute. Changing the `default` value will not update already existing documents.\n",
+ "description": "Update an integer attribute. Changing the `default` value will not update already existing documents.\r\n",
"responses": {
"200": {
"description": "AttributeInteger",
@@ -6426,7 +6426,7 @@
"tags": [
"databases"
],
- "description": "Create IP address attribute.\n",
+ "description": "Create IP address attribute.\r\n",
"responses": {
"202": {
"description": "AttributeIP",
@@ -6535,7 +6535,7 @@
"tags": [
"databases"
],
- "description": "Update an ip attribute. Changing the `default` value will not update already existing documents.\n",
+ "description": "Update an ip attribute. Changing the `default` value will not update already existing documents.\r\n",
"responses": {
"200": {
"description": "AttributeIP",
@@ -6649,7 +6649,7 @@
"tags": [
"databases"
],
- "description": "Create relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\n",
+ "description": "Create relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\r\n",
"responses": {
"202": {
"description": "AttributeRelationship",
@@ -6783,7 +6783,7 @@
"tags": [
"databases"
],
- "description": "Create a string attribute.\n",
+ "description": "Create a string attribute.\r\n",
"responses": {
"202": {
"description": "AttributeString",
@@ -6903,7 +6903,7 @@
"tags": [
"databases"
],
- "description": "Update a string attribute. Changing the `default` value will not update already existing documents.\n",
+ "description": "Update a string attribute. Changing the `default` value will not update already existing documents.\r\n",
"responses": {
"200": {
"description": "AttributeString",
@@ -7022,7 +7022,7 @@
"tags": [
"databases"
],
- "description": "Create a URL attribute.\n",
+ "description": "Create a URL attribute.\r\n",
"responses": {
"202": {
"description": "AttributeURL",
@@ -7131,7 +7131,7 @@
"tags": [
"databases"
],
- "description": "Update an url attribute. Changing the `default` value will not update already existing documents.\n",
+ "description": "Update an url attribute. Changing the `default` value will not update already existing documents.\r\n",
"responses": {
"200": {
"description": "AttributeURL",
@@ -7433,7 +7433,7 @@
"tags": [
"databases"
],
- "description": "Update relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\n",
+ "description": "Update relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\r\n",
"responses": {
"200": {
"description": "AttributeRelationship",
@@ -8119,7 +8119,7 @@
"tags": [
"databases"
],
- "description": "Creates an index on the attributes listed. Your index should include all the attributes you will query in a single request.\nAttributes can be `key`, `fulltext`, and `unique`.",
+ "description": "Creates an index on the attributes listed. Your index should include all the attributes you will query in a single request.\r\nAttributes can be `key`, `fulltext`, and `unique`.",
"responses": {
"202": {
"description": "Index",
@@ -8767,7 +8767,7 @@
"tags": [
"functions"
],
- "description": "List allowed function specifications for this instance.\n",
+ "description": "List allowed function specifications for this instance.\r\n",
"responses": {
"200": {
"description": "Specifications List",
@@ -9249,7 +9249,7 @@
"tags": [
"functions"
],
- "description": "Create a new function code deployment. Use this endpoint to upload a new version of your code function. To execute your newly uploaded code, you'll need to update the function's deployment to use your new deployment UID.\n\nThis endpoint accepts a tar.gz file compressed with your code. Make sure to include any dependencies your code has within the compressed file. You can learn more about code packaging in the [Appwrite Cloud Functions tutorial](https:\/\/appwrite.io\/docs\/functions).\n\nUse the \"command\" param to set the entrypoint used to execute your code.",
+ "description": "Create a new function code deployment. Use this endpoint to upload a new version of your code function. To execute your newly uploaded code, you'll need to update the function's deployment to use your new deployment UID.\r\n\r\nThis endpoint accepts a tar.gz file compressed with your code. Make sure to include any dependencies your code has within the compressed file. You can learn more about code packaging in the [Appwrite Cloud Functions tutorial](https:\/\/appwrite.io\/docs\/functions).\r\n\r\nUse the \"command\" param to set the entrypoint used to execute your code.",
"responses": {
"202": {
"description": "Deployment",
@@ -10068,7 +10068,7 @@
"tags": [
"functions"
],
- "description": "Delete a function execution by its unique ID.\n",
+ "description": "Delete a function execution by its unique ID.\r\n",
"responses": {
"204": {
"description": "No content"
@@ -11279,7 +11279,7 @@
"tags": [
"health"
],
- "description": "Returns the amount of failed jobs in a given queue.\n",
+ "description": "Returns the amount of failed jobs in a given queue.\r\n",
"responses": {
"200": {
"description": "Health Queue",
@@ -12046,7 +12046,7 @@
"tags": [
"locale"
],
- "description": "Get the current user location based on IP. Returns an object with user country code, country name, continent name, continent code, ip address and suggested currency. You can use the locale header to get the data in a supported language.\n\n([IP Geolocation by DB-IP](https:\/\/db-ip.com))",
+ "description": "Get the current user location based on IP. Returns an object with user country code, country name, continent name, continent code, ip address and suggested currency. You can use the locale header to get the data in a supported language.\r\n\r\n([IP Geolocation by DB-IP](https:\/\/db-ip.com))",
"responses": {
"200": {
"description": "Locale",
@@ -12097,7 +12097,7 @@
},
"\/locale\/codes": {
"get": {
- "summary": "List Locale Codes",
+ "summary": "List locale codes",
"operationId": "localeListCodes",
"tags": [
"locale"
@@ -12720,7 +12720,7 @@
"tags": [
"messaging"
],
- "description": "Update an email message by its unique ID.\n",
+ "description": "Update an email message by its unique ID.\r\n",
"responses": {
"200": {
"description": "Message",
@@ -13027,7 +13027,7 @@
"tags": [
"messaging"
],
- "description": "Update a push notification by its unique ID.\n",
+ "description": "Update a push notification by its unique ID.\r\n",
"responses": {
"200": {
"description": "Message",
@@ -13299,7 +13299,7 @@
"tags": [
"messaging"
],
- "description": "Update an email message by its unique ID.\n",
+ "description": "Update an email message by its unique ID.\r\n",
"responses": {
"200": {
"description": "Message",
@@ -13414,7 +13414,7 @@
"tags": [
"messaging"
],
- "description": "Get a message by its unique ID.\n",
+ "description": "Get a message by its unique ID.\r\n",
"responses": {
"200": {
"description": "Message",
@@ -15915,7 +15915,7 @@
"tags": [
"messaging"
],
- "description": "Get a provider by its unique ID.\n",
+ "description": "Get a provider by its unique ID.\r\n",
"responses": {
"200": {
"description": "Provider",
@@ -16355,7 +16355,7 @@
"tags": [
"messaging"
],
- "description": "Get a topic by its unique ID.\n",
+ "description": "Get a topic by its unique ID.\r\n",
"responses": {
"200": {
"description": "Topic",
@@ -16418,7 +16418,7 @@
"tags": [
"messaging"
],
- "description": "Update a topic by its unique ID.\n",
+ "description": "Update a topic by its unique ID.\r\n",
"responses": {
"200": {
"description": "Topic",
@@ -16822,7 +16822,7 @@
"tags": [
"messaging"
],
- "description": "Get a subscriber by its unique ID.\n",
+ "description": "Get a subscriber by its unique ID.\r\n",
"responses": {
"200": {
"description": "Subscriber",
@@ -17516,7 +17516,7 @@
"tags": [
"storage"
],
- "description": "Create a new file. Before using this route, you should create a new bucket resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/storage#storageCreateBucket) API or directly from your Appwrite console.\n\nLarger files should be uploaded using multiple requests with the [content-range](https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Content-Range) header to send a partial request with a maximum supported chunk of `5MB`. The `content-range` header values should always be in bytes.\n\nWhen the first request is sent, the server will return the **File** object, and the subsequent part request must include the file's **id** in `x-appwrite-id` header to allow the server to know that the partial upload is for the existing file and not for a new one.\n\nIf you're creating a new file using one of the Appwrite SDKs, all the chunking logic will be managed by the SDK internally.\n",
+ "description": "Create a new file. Before using this route, you should create a new bucket resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/storage#storageCreateBucket) API or directly from your Appwrite console.\r\n\r\nLarger files should be uploaded using multiple requests with the [content-range](https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Content-Range) header to send a partial request with a maximum supported chunk of `5MB`. The `content-range` header values should always be in bytes.\r\n\r\nWhen the first request is sent, the server will return the **File** object, and the subsequent part request must include the file's **id** in `x-appwrite-id` header to allow the server to know that the partial upload is for the existing file and not for a new one.\r\n\r\nIf you're creating a new file using one of the Appwrite SDKs, all the chunking logic will be managed by the SDK internally.\r\n",
"responses": {
"201": {
"description": "File",
@@ -17789,7 +17789,7 @@
}
},
"delete": {
- "summary": "Delete File",
+ "summary": "Delete file",
"operationId": "storageDeleteFile",
"tags": [
"storage"
@@ -18137,7 +18137,8 @@
"jpeg",
"gif",
"png",
- "webp"
+ "webp",
+ "avif"
],
"x-enum-name": "ImageFormat",
"x-enum-keys": [],
@@ -18697,7 +18698,7 @@
"tags": [
"teams"
],
- "description": "Invite a new member to join your team. Provide an ID for existing users, or invite unregistered users using an email or phone number. If initiated from a Client SDK, Appwrite will send an email or sms with a link to join the team to the invited user, and an account will be created for them if one doesn't exist. If initiated from a Server SDK, the new member will be added automatically to the team.\n\nYou only need to provide one of a user ID, email, or phone number. Appwrite will prioritize accepting the user ID > email > phone number if you provide more than one of these parameters.\n\nUse the `url` parameter to redirect the user from the invitation email to your app. After the user is redirected, use the [Update Team Membership Status](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/teams#updateMembershipStatus) endpoint to allow the user to accept the invitation to the team. \n\nPlease note that to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) Appwrite will accept the only redirect URLs under the domains you have added as a platform on the Appwrite Console.\n",
+ "description": "Invite a new member to join your team. Provide an ID for existing users, or invite unregistered users using an email or phone number. If initiated from a Client SDK, Appwrite will send an email or sms with a link to join the team to the invited user, and an account will be created for them if one doesn't exist. If initiated from a Server SDK, the new member will be added automatically to the team.\r\n\r\nYou only need to provide one of a user ID, email, or phone number. Appwrite will prioritize accepting the user ID > email > phone number if you provide more than one of these parameters.\r\n\r\nUse the `url` parameter to redirect the user from the invitation email to your app. After the user is redirected, use the [Update Team Membership Status](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/teams#updateMembershipStatus) endpoint to allow the user to accept the invitation to the team. \r\n\r\nPlease note that to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) Appwrite will accept the only redirect URLs under the domains you have added as a platform on the Appwrite Console.\r\n",
"responses": {
"201": {
"description": "Membership",
@@ -18888,7 +18889,7 @@
"tags": [
"teams"
],
- "description": "Modify the roles of a team member. Only team members with the owner role have access to this endpoint. Learn more about [roles and permissions](https:\/\/appwrite.io\/docs\/permissions).\n",
+ "description": "Modify the roles of a team member. Only team members with the owner role have access to this endpoint. Learn more about [roles and permissions](https:\/\/appwrite.io\/docs\/permissions).\r\n",
"responses": {
"200": {
"description": "Membership",
@@ -19057,7 +19058,7 @@
"tags": [
"teams"
],
- "description": "Use this endpoint to allow a user to accept an invitation to join a team after being redirected back to your app from the invitation email received by the user.\n\nIf the request is successful, a session for the user is automatically created.\n",
+ "description": "Use this endpoint to allow a user to accept an invitation to join a team after being redirected back to your app from the invitation email received by the user.\r\n\r\nIf the request is successful, a session for the user is automatically created.\r\n",
"responses": {
"200": {
"description": "Membership",
@@ -19645,7 +19646,7 @@
},
"\/users\/identities": {
"get": {
- "summary": "List Identities",
+ "summary": "List identities",
"operationId": "usersListIdentities",
"tags": [
"users"
@@ -20580,7 +20581,7 @@
"tags": [
"users"
],
- "description": "Update the user labels by its unique ID. \n\nLabels can be used to grant access to resources. While teams are a way for user's to share access to a resource, labels can be defined by the developer to grant access without an invitation. See the [Permissions docs](https:\/\/appwrite.io\/docs\/permissions) for more info.",
+ "description": "Update the user labels by its unique ID. \r\n\r\nLabels can be used to grant access to resources. While teams are a way for user's to share access to a resource, labels can be defined by the developer to grant access without an invitation. See the [Permissions docs](https:\/\/appwrite.io\/docs\/permissions) for more info.",
"responses": {
"200": {
"description": "User",
@@ -20885,7 +20886,7 @@
},
"\/users\/{userId}\/mfa\/authenticators\/{type}": {
"delete": {
- "summary": "Delete Authenticator",
+ "summary": "Delete authenticator",
"operationId": "usersDeleteMfaAuthenticator",
"tags": [
"users"
@@ -20964,7 +20965,7 @@
},
"\/users\/{userId}\/mfa\/factors": {
"get": {
- "summary": "List Factors",
+ "summary": "List factors",
"operationId": "usersListMfaFactors",
"tags": [
"users"
@@ -21028,7 +21029,7 @@
},
"\/users\/{userId}\/mfa\/recovery-codes": {
"get": {
- "summary": "Get MFA Recovery Codes",
+ "summary": "Get MFA recovery codes",
"operationId": "usersGetMfaRecoveryCodes",
"tags": [
"users"
@@ -21090,7 +21091,7 @@
]
},
"put": {
- "summary": "Regenerate MFA Recovery Codes",
+ "summary": "Regenerate MFA recovery codes",
"operationId": "usersUpdateMfaRecoveryCodes",
"tags": [
"users"
@@ -21152,7 +21153,7 @@
]
},
"patch": {
- "summary": "Create MFA Recovery Codes",
+ "summary": "Create MFA recovery codes",
"operationId": "usersCreateMfaRecoveryCodes",
"tags": [
"users"
@@ -21677,7 +21678,7 @@
"tags": [
"users"
],
- "description": "Creates a session for a user. Returns an immediately usable session object.\n\nIf you want to generate a token for a custom authentication flow, use the [POST \/users\/{userId}\/tokens](https:\/\/appwrite.io\/docs\/server\/users#createToken) endpoint.",
+ "description": "Creates a session for a user. Returns an immediately usable session object.\r\n\r\nIf you want to generate a token for a custom authentication flow, use the [POST \/users\/{userId}\/tokens](https:\/\/appwrite.io\/docs\/server\/users#createToken) endpoint.",
"responses": {
"201": {
"description": "Session",
@@ -21941,7 +21942,7 @@
},
"\/users\/{userId}\/targets": {
"get": {
- "summary": "List User Targets",
+ "summary": "List user targets",
"operationId": "usersListTargets",
"tags": [
"users"
@@ -22017,7 +22018,7 @@
]
},
"post": {
- "summary": "Create User Target",
+ "summary": "Create user target",
"operationId": "usersCreateTarget",
"tags": [
"users"
@@ -22130,7 +22131,7 @@
},
"\/users\/{userId}\/targets\/{targetId}": {
"get": {
- "summary": "Get User Target",
+ "summary": "Get user target",
"operationId": "usersGetTarget",
"tags": [
"users"
@@ -22203,7 +22204,7 @@
]
},
"patch": {
- "summary": "Update User target",
+ "summary": "Update user target",
"operationId": "usersUpdateTarget",
"tags": [
"users"
@@ -22375,7 +22376,7 @@
"tags": [
"users"
],
- "description": "Returns a token with a secret key for creating a session. Use the user ID and secret and submit a request to the [PUT \/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process.\n",
+ "description": "Returns a token with a secret key for creating a session. Use the user ID and secret and submit a request to the [PUT \/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process.\r\n",
"responses": {
"201": {
"description": "Token",
@@ -23677,6 +23678,16 @@
"x-example": false,
"nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"size": {
"type": "integer",
"description": "Attribute size.",
@@ -23696,6 +23707,8 @@
"status",
"error",
"required",
+ "$createdAt",
+ "$updatedAt",
"size"
]
},
@@ -23734,6 +23747,16 @@
"x-example": false,
"nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"min": {
"type": "integer",
"description": "Minimum value to enforce for new documents.",
@@ -23761,7 +23784,9 @@
"type",
"status",
"error",
- "required"
+ "required",
+ "$createdAt",
+ "$updatedAt"
]
},
"attributeFloat": {
@@ -23799,6 +23824,16 @@
"x-example": false,
"nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"min": {
"type": "number",
"description": "Minimum value to enforce for new documents.",
@@ -23826,7 +23861,9 @@
"type",
"status",
"error",
- "required"
+ "required",
+ "$createdAt",
+ "$updatedAt"
]
},
"attributeBoolean": {
@@ -23864,6 +23901,16 @@
"x-example": false,
"nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"default": {
"type": "boolean",
"description": "Default value for attribute when not provided. Cannot be set when attribute is required.",
@@ -23876,7 +23923,9 @@
"type",
"status",
"error",
- "required"
+ "required",
+ "$createdAt",
+ "$updatedAt"
]
},
"attributeEmail": {
@@ -23914,6 +23963,16 @@
"x-example": false,
"nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"format": {
"type": "string",
"description": "String format.",
@@ -23932,6 +23991,8 @@
"status",
"error",
"required",
+ "$createdAt",
+ "$updatedAt",
"format"
]
},
@@ -23970,6 +24031,16 @@
"x-example": false,
"nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"elements": {
"type": "array",
"description": "Array of elements in enumerated type.",
@@ -23996,6 +24067,8 @@
"status",
"error",
"required",
+ "$createdAt",
+ "$updatedAt",
"elements",
"format"
]
@@ -24035,6 +24108,16 @@
"x-example": false,
"nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"format": {
"type": "string",
"description": "String format.",
@@ -24053,6 +24136,8 @@
"status",
"error",
"required",
+ "$createdAt",
+ "$updatedAt",
"format"
]
},
@@ -24091,6 +24176,16 @@
"x-example": false,
"nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"format": {
"type": "string",
"description": "String format.",
@@ -24109,6 +24204,8 @@
"status",
"error",
"required",
+ "$createdAt",
+ "$updatedAt",
"format"
]
},
@@ -24147,6 +24244,16 @@
"x-example": false,
"nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"format": {
"type": "string",
"description": "ISO 8601 format.",
@@ -24165,6 +24272,8 @@
"status",
"error",
"required",
+ "$createdAt",
+ "$updatedAt",
"format"
]
},
@@ -24203,6 +24312,16 @@
"x-example": false,
"nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"relatedCollection": {
"type": "string",
"description": "The ID of the related collection.",
@@ -24240,6 +24359,8 @@
"status",
"error",
"required",
+ "$createdAt",
+ "$updatedAt",
"relatedCollection",
"relationType",
"twoWay",
@@ -24288,6 +24409,16 @@
},
"x-example": [],
"nullable": true
+ },
+ "$createdAt": {
+ "type": "string",
+ "description": "Index creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Index update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
}
},
"required": [
@@ -24295,7 +24426,9 @@
"type",
"status",
"error",
- "attributes"
+ "attributes",
+ "$createdAt",
+ "$updatedAt"
]
},
"document": {
@@ -25966,7 +26099,7 @@
"responseBody": {
"type": "string",
"description": "HTTP response body. This will return empty unless execution is created as synchronous.",
- "x-example": "Developers are awesome."
+ "x-example": ""
},
"responseHeaders": {
"type": "array",
diff --git a/app/config/specs/open-api3-latest-client.json b/app/config/specs/open-api3-latest-client.json
index 8a9967090d..021ae27c45 100644
--- a/app/config/specs/open-api3-latest-client.json
+++ b/app/config/specs/open-api3-latest-client.json
@@ -166,7 +166,7 @@
"tags": [
"account"
],
- "description": "Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. For security measures, user password is required to complete this request.\nThis endpoint can also be used to convert an anonymous account to a normal one, by passing an email address and a new password.\n",
+ "description": "Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. For security measures, user password is required to complete this request.\r\nThis endpoint can also be used to convert an anonymous account to a normal one, by passing an email address and a new password.\r\n",
"responses": {
"200": {
"description": "User",
@@ -239,7 +239,7 @@
},
"\/account\/identities": {
"get": {
- "summary": "List Identities",
+ "summary": "List identities",
"operationId": "accountListIdentities",
"tags": [
"account"
@@ -556,7 +556,7 @@
},
"\/account\/mfa\/authenticators\/{type}": {
"post": {
- "summary": "Create Authenticator",
+ "summary": "Create authenticator",
"operationId": "accountCreateMfaAuthenticator",
"tags": [
"account"
@@ -624,7 +624,7 @@
]
},
"put": {
- "summary": "Verify Authenticator",
+ "summary": "Verify authenticator",
"operationId": "accountUpdateMfaAuthenticator",
"tags": [
"account"
@@ -711,7 +711,7 @@
}
},
"delete": {
- "summary": "Delete Authenticator",
+ "summary": "Delete authenticator",
"operationId": "accountDeleteMfaAuthenticator",
"tags": [
"account"
@@ -774,7 +774,7 @@
},
"\/account\/mfa\/challenge": {
"post": {
- "summary": "Create MFA Challenge",
+ "summary": "Create MFA challenge",
"operationId": "accountCreateMfaChallenge",
"tags": [
"account"
@@ -850,7 +850,7 @@
}
},
"put": {
- "summary": "Create MFA Challenge (confirmation)",
+ "summary": "Create MFA challenge (confirmation)",
"operationId": "accountUpdateMfaChallenge",
"tags": [
"account"
@@ -928,7 +928,7 @@
},
"\/account\/mfa\/factors": {
"get": {
- "summary": "List Factors",
+ "summary": "List factors",
"operationId": "accountListMfaFactors",
"tags": [
"account"
@@ -981,7 +981,7 @@
},
"\/account\/mfa\/recovery-codes": {
"get": {
- "summary": "Get MFA Recovery Codes",
+ "summary": "Get MFA recovery codes",
"operationId": "accountGetMfaRecoveryCodes",
"tags": [
"account"
@@ -1032,7 +1032,7 @@
]
},
"post": {
- "summary": "Create MFA Recovery Codes",
+ "summary": "Create MFA recovery codes",
"operationId": "accountCreateMfaRecoveryCodes",
"tags": [
"account"
@@ -1083,7 +1083,7 @@
]
},
"patch": {
- "summary": "Regenerate MFA Recovery Codes",
+ "summary": "Regenerate MFA recovery codes",
"operationId": "accountUpdateMfaRecoveryCodes",
"tags": [
"account"
@@ -1570,7 +1570,7 @@
"tags": [
"account"
],
- "description": "Use this endpoint to complete the user account password reset. Both the **userId** and **secret** arguments will be passed as query parameters to the redirect URL you have provided when sending your request to the [POST \/account\/recovery](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createRecovery) endpoint.\n\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.",
+ "description": "Use this endpoint to complete the user account password reset. Both the **userId** and **secret** arguments will be passed as query parameters to the redirect URL you have provided when sending your request to the [POST \/account\/recovery](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createRecovery) endpoint.\r\n\r\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.",
"responses": {
"200": {
"description": "Token",
@@ -1802,7 +1802,7 @@
"tags": [
"account"
],
- "description": "Allow the user to login into their account by providing a valid email and password combination. This route will create a new session for the user.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
+ "description": "Allow the user to login into their account by providing a valid email and password combination. This route will create a new session for the user.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
"responses": {
"201": {
"description": "Session",
@@ -1954,7 +1954,7 @@
"tags": [
"account"
],
- "description": "Allow the user to login to their account using the OAuth2 provider of their choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed.\n\nIf there is already an active session, the new session will be attached to the logged-in account. If there are no active sessions, the server will attempt to look for a user with the same email address as the email received from the OAuth2 provider and attach the new session to the existing user. If no matching user is found - the server will create a new user.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\n",
+ "description": "Allow the user to login to their account using the OAuth2 provider of their choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed.\r\n\r\nIf there is already an active session, the new session will be attached to the logged-in account. If there are no active sessions, the server will attempt to look for a user with the same email address as the email received from the OAuth2 provider and attach the new session to the existing user. If no matching user is found - the server will create a new user.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\r\n",
"responses": {
"301": {
"description": "File"
@@ -2703,7 +2703,7 @@
"tags": [
"account"
],
- "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's email is valid for 15 minutes.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
+ "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's email is valid for 15 minutes.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
"responses": {
"201": {
"description": "Token",
@@ -2784,7 +2784,7 @@
"tags": [
"account"
],
- "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour. If you are on a mobile device you can leave the URL parameter empty, so that the login completion will be handled by your Appwrite instance by default.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\n",
+ "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour. If you are on a mobile device you can leave the URL parameter empty, so that the login completion will be handled by your Appwrite instance by default.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\r\n",
"responses": {
"201": {
"description": "Token",
@@ -2873,7 +2873,7 @@
"tags": [
"account"
],
- "description": "Allow the user to login to their account using the OAuth2 provider of their choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed. \n\nIf authentication succeeds, `userId` and `secret` of a token will be appended to the success URL as query parameters. These can be used to create a new session using the [Create session](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
+ "description": "Allow the user to login to their account using the OAuth2 provider of their choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed. \r\n\r\nIf authentication succeeds, `userId` and `secret` of a token will be appended to the success URL as query parameters. These can be used to create a new session using the [Create session](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
"responses": {
"301": {
"description": "File"
@@ -3009,7 +3009,7 @@
"tags": [
"account"
],
- "description": "Sends the user an SMS with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's phone is valid for 15 minutes.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
+ "description": "Sends the user an SMS with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's phone is valid for 15 minutes.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
"responses": {
"201": {
"description": "Token",
@@ -3088,7 +3088,7 @@
"tags": [
"account"
],
- "description": "Use this endpoint to send a verification message to your user email address to confirm they are the valid owners of that address. Both the **userId** and **secret** arguments will be passed as query parameters to the URL you have provided to be attached to the verification email. The provided URL should redirect the user back to your app and allow you to complete the verification process by verifying both the **userId** and **secret** parameters. Learn more about how to [complete the verification process](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\n\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.\n",
+ "description": "Use this endpoint to send a verification message to your user email address to confirm they are the valid owners of that address. Both the **userId** and **secret** arguments will be passed as query parameters to the URL you have provided to be attached to the verification email. The provided URL should redirect the user back to your app and allow you to complete the verification process by verifying both the **userId** and **secret** parameters. Learn more about how to [complete the verification process](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\r\n\r\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.\r\n",
"responses": {
"201": {
"description": "Token",
@@ -3368,7 +3368,7 @@
"tags": [
"avatars"
],
- "description": "You can use this endpoint to show different browser icons to your users. The code argument receives the browser code as it appears in your user [GET \/account\/sessions](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#getSessions) endpoint. Use width, height and quality arguments to change the output settings.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.",
+ "description": "You can use this endpoint to show different browser icons to your users. The code argument receives the browser code as it appears in your user [GET \/account\/sessions](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#getSessions) endpoint. Use width, height and quality arguments to change the output settings.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.",
"responses": {
"200": {
"description": "Image"
@@ -3496,7 +3496,7 @@
"tags": [
"avatars"
],
- "description": "The credit card endpoint will return you the icon of the credit card provider you need. Use width, height and quality arguments to change the output settings.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n",
+ "description": "The credit card endpoint will return you the icon of the credit card provider you need. Use width, height and quality arguments to change the output settings.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n",
"responses": {
"200": {
"description": "Image"
@@ -3628,7 +3628,7 @@
"tags": [
"avatars"
],
- "description": "Use this endpoint to fetch the favorite icon (AKA favicon) of any remote website URL.\n\nThis endpoint does not follow HTTP redirects.",
+ "description": "Use this endpoint to fetch the favorite icon (AKA favicon) of any remote website URL.\r\n\r\nThis endpoint does not follow HTTP redirects.",
"responses": {
"200": {
"description": "Image"
@@ -3688,7 +3688,7 @@
"tags": [
"avatars"
],
- "description": "You can use this endpoint to show different country flags icons to your users. The code argument receives the 2 letter country code. Use width, height and quality arguments to change the output settings. Country codes follow the [ISO 3166-1](https:\/\/en.wikipedia.org\/wiki\/ISO_3166-1) standard.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n",
+ "description": "You can use this endpoint to show different country flags icons to your users. The code argument receives the 2 letter country code. Use width, height and quality arguments to change the output settings. Country codes follow the [ISO 3166-1](https:\/\/en.wikipedia.org\/wiki\/ISO_3166-1) standard.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n",
"responses": {
"200": {
"description": "Image"
@@ -4178,7 +4178,7 @@
"tags": [
"avatars"
],
- "description": "Use this endpoint to fetch a remote image URL and crop it to any image size you want. This endpoint is very useful if you need to crop and display remote images in your app or in case you want to make sure a 3rd party image is properly served using a TLS protocol.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 400x400px.\n\nThis endpoint does not follow HTTP redirects.",
+ "description": "Use this endpoint to fetch a remote image URL and crop it to any image size you want. This endpoint is very useful if you need to crop and display remote images in your app or in case you want to make sure a 3rd party image is properly served using a TLS protocol.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 400x400px.\r\n\r\nThis endpoint does not follow HTTP redirects.",
"responses": {
"200": {
"description": "Image"
@@ -4262,7 +4262,7 @@
"tags": [
"avatars"
],
- "description": "Use this endpoint to show your user initials avatar icon on your website or app. By default, this route will try to print your logged-in user name or email initials. You can also overwrite the user name if you pass the 'name' parameter. If no name is given and no user is logged, an empty avatar will be returned.\n\nYou can use the color and background params to change the avatar colors. By default, a random theme will be selected. The random theme will persist for the user's initials when reloading the same theme will always return for the same initials.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n",
+ "description": "Use this endpoint to show your user initials avatar icon on your website or app. By default, this route will try to print your logged-in user name or email initials. You can also overwrite the user name if you pass the 'name' parameter. If no name is given and no user is logged, an empty avatar will be returned.\r\n\r\nYou can use the color and background params to change the avatar colors. By default, a random theme will be selected. The random theme will persist for the user's initials when reloading the same theme will always return for the same initials.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n",
"responses": {
"200": {
"description": "Image"
@@ -4356,7 +4356,7 @@
"tags": [
"avatars"
],
- "description": "Converts a given plain text to a QR code image. You can use the query parameters to change the size and style of the resulting image.\n",
+ "description": "Converts a given plain text to a QR code image. You can use the query parameters to change the size and style of the resulting image.\r\n",
"responses": {
"200": {
"description": "Image"
@@ -5319,7 +5319,7 @@
"tags": [
"locale"
],
- "description": "Get the current user location based on IP. Returns an object with user country code, country name, continent name, continent code, ip address and suggested currency. You can use the locale header to get the data in a supported language.\n\n([IP Geolocation by DB-IP](https:\/\/db-ip.com))",
+ "description": "Get the current user location based on IP. Returns an object with user country code, country name, continent name, continent code, ip address and suggested currency. You can use the locale header to get the data in a supported language.\r\n\r\n([IP Geolocation by DB-IP](https:\/\/db-ip.com))",
"responses": {
"200": {
"description": "Locale",
@@ -5368,7 +5368,7 @@
},
"\/locale\/codes": {
"get": {
- "summary": "List Locale Codes",
+ "summary": "List locale codes",
"operationId": "localeListCodes",
"tags": [
"locale"
@@ -6001,7 +6001,7 @@
"tags": [
"storage"
],
- "description": "Create a new file. Before using this route, you should create a new bucket resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/storage#storageCreateBucket) API or directly from your Appwrite console.\n\nLarger files should be uploaded using multiple requests with the [content-range](https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Content-Range) header to send a partial request with a maximum supported chunk of `5MB`. The `content-range` header values should always be in bytes.\n\nWhen the first request is sent, the server will return the **File** object, and the subsequent part request must include the file's **id** in `x-appwrite-id` header to allow the server to know that the partial upload is for the existing file and not for a new one.\n\nIf you're creating a new file using one of the Appwrite SDKs, all the chunking logic will be managed by the SDK internally.\n",
+ "description": "Create a new file. Before using this route, you should create a new bucket resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/storage#storageCreateBucket) API or directly from your Appwrite console.\r\n\r\nLarger files should be uploaded using multiple requests with the [content-range](https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Content-Range) header to send a partial request with a maximum supported chunk of `5MB`. The `content-range` header values should always be in bytes.\r\n\r\nWhen the first request is sent, the server will return the **File** object, and the subsequent part request must include the file's **id** in `x-appwrite-id` header to allow the server to know that the partial upload is for the existing file and not for a new one.\r\n\r\nIf you're creating a new file using one of the Appwrite SDKs, all the chunking logic will be managed by the SDK internally.\r\n",
"responses": {
"201": {
"description": "File",
@@ -6268,7 +6268,7 @@
}
},
"delete": {
- "summary": "Delete File",
+ "summary": "Delete file",
"operationId": "storageDeleteFile",
"tags": [
"storage"
@@ -6610,7 +6610,8 @@
"jpeg",
"gif",
"png",
- "webp"
+ "webp",
+ "avif"
],
"x-enum-name": "ImageFormat",
"x-enum-keys": [],
@@ -7156,7 +7157,7 @@
"tags": [
"teams"
],
- "description": "Invite a new member to join your team. Provide an ID for existing users, or invite unregistered users using an email or phone number. If initiated from a Client SDK, Appwrite will send an email or sms with a link to join the team to the invited user, and an account will be created for them if one doesn't exist. If initiated from a Server SDK, the new member will be added automatically to the team.\n\nYou only need to provide one of a user ID, email, or phone number. Appwrite will prioritize accepting the user ID > email > phone number if you provide more than one of these parameters.\n\nUse the `url` parameter to redirect the user from the invitation email to your app. After the user is redirected, use the [Update Team Membership Status](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/teams#updateMembershipStatus) endpoint to allow the user to accept the invitation to the team. \n\nPlease note that to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) Appwrite will accept the only redirect URLs under the domains you have added as a platform on the Appwrite Console.\n",
+ "description": "Invite a new member to join your team. Provide an ID for existing users, or invite unregistered users using an email or phone number. If initiated from a Client SDK, Appwrite will send an email or sms with a link to join the team to the invited user, and an account will be created for them if one doesn't exist. If initiated from a Server SDK, the new member will be added automatically to the team.\r\n\r\nYou only need to provide one of a user ID, email, or phone number. Appwrite will prioritize accepting the user ID > email > phone number if you provide more than one of these parameters.\r\n\r\nUse the `url` parameter to redirect the user from the invitation email to your app. After the user is redirected, use the [Update Team Membership Status](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/teams#updateMembershipStatus) endpoint to allow the user to accept the invitation to the team. \r\n\r\nPlease note that to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) Appwrite will accept the only redirect URLs under the domains you have added as a platform on the Appwrite Console.\r\n",
"responses": {
"201": {
"description": "Membership",
@@ -7343,7 +7344,7 @@
"tags": [
"teams"
],
- "description": "Modify the roles of a team member. Only team members with the owner role have access to this endpoint. Learn more about [roles and permissions](https:\/\/appwrite.io\/docs\/permissions).\n",
+ "description": "Modify the roles of a team member. Only team members with the owner role have access to this endpoint. Learn more about [roles and permissions](https:\/\/appwrite.io\/docs\/permissions).\r\n",
"responses": {
"200": {
"description": "Membership",
@@ -7508,7 +7509,7 @@
"tags": [
"teams"
],
- "description": "Use this endpoint to allow a user to accept an invitation to join a team after being redirected back to your app from the invitation email received by the user.\n\nIf the request is successful, a session for the user is automatically created.\n",
+ "description": "Use this endpoint to allow a user to accept an invitation to join a team after being redirected back to your app from the invitation email received by the user.\r\n\r\nIf the request is successful, a session for the user is automatically created.\r\n",
"responses": {
"200": {
"description": "Membership",
@@ -9405,7 +9406,7 @@
"responseBody": {
"type": "string",
"description": "HTTP response body. This will return empty unless execution is created as synchronous.",
- "x-example": "Developers are awesome."
+ "x-example": ""
},
"responseHeaders": {
"type": "array",
diff --git a/app/config/specs/open-api3-latest-console.json b/app/config/specs/open-api3-latest-console.json
index 07749889d8..04e7c76e13 100644
--- a/app/config/specs/open-api3-latest-console.json
+++ b/app/config/specs/open-api3-latest-console.json
@@ -206,7 +206,7 @@
"tags": [
"account"
],
- "description": "Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. For security measures, user password is required to complete this request.\nThis endpoint can also be used to convert an anonymous account to a normal one, by passing an email address and a new password.\n",
+ "description": "Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. For security measures, user password is required to complete this request.\r\nThis endpoint can also be used to convert an anonymous account to a normal one, by passing an email address and a new password.\r\n",
"responses": {
"200": {
"description": "User",
@@ -278,7 +278,7 @@
},
"\/account\/identities": {
"get": {
- "summary": "List Identities",
+ "summary": "List identities",
"operationId": "accountListIdentities",
"tags": [
"account"
@@ -591,7 +591,7 @@
},
"\/account\/mfa\/authenticators\/{type}": {
"post": {
- "summary": "Create Authenticator",
+ "summary": "Create authenticator",
"operationId": "accountCreateMfaAuthenticator",
"tags": [
"account"
@@ -658,7 +658,7 @@
]
},
"put": {
- "summary": "Verify Authenticator",
+ "summary": "Verify authenticator",
"operationId": "accountUpdateMfaAuthenticator",
"tags": [
"account"
@@ -744,7 +744,7 @@
}
},
"delete": {
- "summary": "Delete Authenticator",
+ "summary": "Delete authenticator",
"operationId": "accountDeleteMfaAuthenticator",
"tags": [
"account"
@@ -806,7 +806,7 @@
},
"\/account\/mfa\/challenge": {
"post": {
- "summary": "Create MFA Challenge",
+ "summary": "Create MFA challenge",
"operationId": "accountCreateMfaChallenge",
"tags": [
"account"
@@ -882,7 +882,7 @@
}
},
"put": {
- "summary": "Create MFA Challenge (confirmation)",
+ "summary": "Create MFA challenge (confirmation)",
"operationId": "accountUpdateMfaChallenge",
"tags": [
"account"
@@ -959,7 +959,7 @@
},
"\/account\/mfa\/factors": {
"get": {
- "summary": "List Factors",
+ "summary": "List factors",
"operationId": "accountListMfaFactors",
"tags": [
"account"
@@ -1011,7 +1011,7 @@
},
"\/account\/mfa\/recovery-codes": {
"get": {
- "summary": "Get MFA Recovery Codes",
+ "summary": "Get MFA recovery codes",
"operationId": "accountGetMfaRecoveryCodes",
"tags": [
"account"
@@ -1061,7 +1061,7 @@
]
},
"post": {
- "summary": "Create MFA Recovery Codes",
+ "summary": "Create MFA recovery codes",
"operationId": "accountCreateMfaRecoveryCodes",
"tags": [
"account"
@@ -1111,7 +1111,7 @@
]
},
"patch": {
- "summary": "Regenerate MFA Recovery Codes",
+ "summary": "Regenerate MFA recovery codes",
"operationId": "accountUpdateMfaRecoveryCodes",
"tags": [
"account"
@@ -1591,7 +1591,7 @@
"tags": [
"account"
],
- "description": "Use this endpoint to complete the user account password reset. Both the **userId** and **secret** arguments will be passed as query parameters to the redirect URL you have provided when sending your request to the [POST \/account\/recovery](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createRecovery) endpoint.\n\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.",
+ "description": "Use this endpoint to complete the user account password reset. Both the **userId** and **secret** arguments will be passed as query parameters to the redirect URL you have provided when sending your request to the [POST \/account\/recovery](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createRecovery) endpoint.\r\n\r\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.",
"responses": {
"200": {
"description": "Token",
@@ -1820,7 +1820,7 @@
"tags": [
"account"
],
- "description": "Allow the user to login into their account by providing a valid email and password combination. This route will create a new session for the user.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
+ "description": "Allow the user to login into their account by providing a valid email and password combination. This route will create a new session for the user.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
"responses": {
"201": {
"description": "Session",
@@ -1972,7 +1972,7 @@
"tags": [
"account"
],
- "description": "Allow the user to login to their account using the OAuth2 provider of their choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed.\n\nIf there is already an active session, the new session will be attached to the logged-in account. If there are no active sessions, the server will attempt to look for a user with the same email address as the email received from the OAuth2 provider and attach the new session to the existing user. If no matching user is found - the server will create a new user.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\n",
+ "description": "Allow the user to login to their account using the OAuth2 provider of their choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed.\r\n\r\nIf there is already an active session, the new session will be attached to the logged-in account. If there are no active sessions, the server will attempt to look for a user with the same email address as the email received from the OAuth2 provider and attach the new session to the existing user. If no matching user is found - the server will create a new user.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\r\n",
"responses": {
"301": {
"description": "File"
@@ -2714,7 +2714,7 @@
"tags": [
"account"
],
- "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's email is valid for 15 minutes.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
+ "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's email is valid for 15 minutes.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
"responses": {
"201": {
"description": "Token",
@@ -2795,7 +2795,7 @@
"tags": [
"account"
],
- "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour. If you are on a mobile device you can leave the URL parameter empty, so that the login completion will be handled by your Appwrite instance by default.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\n",
+ "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour. If you are on a mobile device you can leave the URL parameter empty, so that the login completion will be handled by your Appwrite instance by default.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\r\n",
"responses": {
"201": {
"description": "Token",
@@ -2884,7 +2884,7 @@
"tags": [
"account"
],
- "description": "Allow the user to login to their account using the OAuth2 provider of their choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed. \n\nIf authentication succeeds, `userId` and `secret` of a token will be appended to the success URL as query parameters. These can be used to create a new session using the [Create session](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
+ "description": "Allow the user to login to their account using the OAuth2 provider of their choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed. \r\n\r\nIf authentication succeeds, `userId` and `secret` of a token will be appended to the success URL as query parameters. These can be used to create a new session using the [Create session](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
"responses": {
"301": {
"description": "File"
@@ -3020,7 +3020,7 @@
"tags": [
"account"
],
- "description": "Sends the user an SMS with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's phone is valid for 15 minutes.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
+ "description": "Sends the user an SMS with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's phone is valid for 15 minutes.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
"responses": {
"201": {
"description": "Token",
@@ -3099,7 +3099,7 @@
"tags": [
"account"
],
- "description": "Use this endpoint to send a verification message to your user email address to confirm they are the valid owners of that address. Both the **userId** and **secret** arguments will be passed as query parameters to the URL you have provided to be attached to the verification email. The provided URL should redirect the user back to your app and allow you to complete the verification process by verifying both the **userId** and **secret** parameters. Learn more about how to [complete the verification process](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\n\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.\n",
+ "description": "Use this endpoint to send a verification message to your user email address to confirm they are the valid owners of that address. Both the **userId** and **secret** arguments will be passed as query parameters to the URL you have provided to be attached to the verification email. The provided URL should redirect the user back to your app and allow you to complete the verification process by verifying both the **userId** and **secret** parameters. Learn more about how to [complete the verification process](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\r\n\r\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.\r\n",
"responses": {
"201": {
"description": "Token",
@@ -3375,7 +3375,7 @@
"tags": [
"avatars"
],
- "description": "You can use this endpoint to show different browser icons to your users. The code argument receives the browser code as it appears in your user [GET \/account\/sessions](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#getSessions) endpoint. Use width, height and quality arguments to change the output settings.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.",
+ "description": "You can use this endpoint to show different browser icons to your users. The code argument receives the browser code as it appears in your user [GET \/account\/sessions](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#getSessions) endpoint. Use width, height and quality arguments to change the output settings.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.",
"responses": {
"200": {
"description": "Image"
@@ -3503,7 +3503,7 @@
"tags": [
"avatars"
],
- "description": "The credit card endpoint will return you the icon of the credit card provider you need. Use width, height and quality arguments to change the output settings.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n",
+ "description": "The credit card endpoint will return you the icon of the credit card provider you need. Use width, height and quality arguments to change the output settings.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n",
"responses": {
"200": {
"description": "Image"
@@ -3635,7 +3635,7 @@
"tags": [
"avatars"
],
- "description": "Use this endpoint to fetch the favorite icon (AKA favicon) of any remote website URL.\n\nThis endpoint does not follow HTTP redirects.",
+ "description": "Use this endpoint to fetch the favorite icon (AKA favicon) of any remote website URL.\r\n\r\nThis endpoint does not follow HTTP redirects.",
"responses": {
"200": {
"description": "Image"
@@ -3695,7 +3695,7 @@
"tags": [
"avatars"
],
- "description": "You can use this endpoint to show different country flags icons to your users. The code argument receives the 2 letter country code. Use width, height and quality arguments to change the output settings. Country codes follow the [ISO 3166-1](https:\/\/en.wikipedia.org\/wiki\/ISO_3166-1) standard.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n",
+ "description": "You can use this endpoint to show different country flags icons to your users. The code argument receives the 2 letter country code. Use width, height and quality arguments to change the output settings. Country codes follow the [ISO 3166-1](https:\/\/en.wikipedia.org\/wiki\/ISO_3166-1) standard.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n",
"responses": {
"200": {
"description": "Image"
@@ -4185,7 +4185,7 @@
"tags": [
"avatars"
],
- "description": "Use this endpoint to fetch a remote image URL and crop it to any image size you want. This endpoint is very useful if you need to crop and display remote images in your app or in case you want to make sure a 3rd party image is properly served using a TLS protocol.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 400x400px.\n\nThis endpoint does not follow HTTP redirects.",
+ "description": "Use this endpoint to fetch a remote image URL and crop it to any image size you want. This endpoint is very useful if you need to crop and display remote images in your app or in case you want to make sure a 3rd party image is properly served using a TLS protocol.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 400x400px.\r\n\r\nThis endpoint does not follow HTTP redirects.",
"responses": {
"200": {
"description": "Image"
@@ -4269,7 +4269,7 @@
"tags": [
"avatars"
],
- "description": "Use this endpoint to show your user initials avatar icon on your website or app. By default, this route will try to print your logged-in user name or email initials. You can also overwrite the user name if you pass the 'name' parameter. If no name is given and no user is logged, an empty avatar will be returned.\n\nYou can use the color and background params to change the avatar colors. By default, a random theme will be selected. The random theme will persist for the user's initials when reloading the same theme will always return for the same initials.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n",
+ "description": "Use this endpoint to show your user initials avatar icon on your website or app. By default, this route will try to print your logged-in user name or email initials. You can also overwrite the user name if you pass the 'name' parameter. If no name is given and no user is logged, an empty avatar will be returned.\r\n\r\nYou can use the color and background params to change the avatar colors. By default, a random theme will be selected. The random theme will persist for the user's initials when reloading the same theme will always return for the same initials.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n",
"responses": {
"200": {
"description": "Image"
@@ -4363,7 +4363,7 @@
"tags": [
"avatars"
],
- "description": "Converts a given plain text to a QR code image. You can use the query parameters to change the size and style of the resulting image.\n",
+ "description": "Converts a given plain text to a QR code image. You can use the query parameters to change the size and style of the resulting image.\r\n",
"responses": {
"200": {
"description": "Image"
@@ -4452,7 +4452,7 @@
},
"\/console\/assistant": {
"post": {
- "summary": "Ask Query",
+ "summary": "Ask query",
"operationId": "assistantChat",
"tags": [
"assistant"
@@ -4644,7 +4644,7 @@
"tags": [
"databases"
],
- "description": "Create a new Database.\n",
+ "description": "Create a new Database.\r\n",
"responses": {
"201": {
"description": "Database",
@@ -5523,7 +5523,7 @@
"tags": [
"databases"
],
- "description": "Create a boolean attribute.\n",
+ "description": "Create a boolean attribute.\r\n",
"responses": {
"202": {
"description": "AttributeBoolean",
@@ -5965,7 +5965,7 @@
"tags": [
"databases"
],
- "description": "Create an email attribute.\n",
+ "description": "Create an email attribute.\r\n",
"responses": {
"202": {
"description": "AttributeEmail",
@@ -6073,7 +6073,7 @@
"tags": [
"databases"
],
- "description": "Update an email attribute. Changing the `default` value will not update already existing documents.\n",
+ "description": "Update an email attribute. Changing the `default` value will not update already existing documents.\r\n",
"responses": {
"200": {
"description": "AttributeEmail",
@@ -6186,7 +6186,7 @@
"tags": [
"databases"
],
- "description": "Create an enumeration attribute. The `elements` param acts as a white-list of accepted values for this attribute. \n",
+ "description": "Create an enumeration attribute. The `elements` param acts as a white-list of accepted values for this attribute. \r\n",
"responses": {
"202": {
"description": "AttributeEnum",
@@ -6303,7 +6303,7 @@
"tags": [
"databases"
],
- "description": "Update an enum attribute. Changing the `default` value will not update already existing documents.\n",
+ "description": "Update an enum attribute. Changing the `default` value will not update already existing documents.\r\n",
"responses": {
"200": {
"description": "AttributeEnum",
@@ -6425,7 +6425,7 @@
"tags": [
"databases"
],
- "description": "Create a float attribute. Optionally, minimum and maximum values can be provided.\n",
+ "description": "Create a float attribute. Optionally, minimum and maximum values can be provided.\r\n",
"responses": {
"202": {
"description": "AttributeFloat",
@@ -6543,7 +6543,7 @@
"tags": [
"databases"
],
- "description": "Update a float attribute. Changing the `default` value will not update already existing documents.\n",
+ "description": "Update a float attribute. Changing the `default` value will not update already existing documents.\r\n",
"responses": {
"200": {
"description": "AttributeFloat",
@@ -6668,7 +6668,7 @@
"tags": [
"databases"
],
- "description": "Create an integer attribute. Optionally, minimum and maximum values can be provided.\n",
+ "description": "Create an integer attribute. Optionally, minimum and maximum values can be provided.\r\n",
"responses": {
"202": {
"description": "AttributeInteger",
@@ -6786,7 +6786,7 @@
"tags": [
"databases"
],
- "description": "Update an integer attribute. Changing the `default` value will not update already existing documents.\n",
+ "description": "Update an integer attribute. Changing the `default` value will not update already existing documents.\r\n",
"responses": {
"200": {
"description": "AttributeInteger",
@@ -6911,7 +6911,7 @@
"tags": [
"databases"
],
- "description": "Create IP address attribute.\n",
+ "description": "Create IP address attribute.\r\n",
"responses": {
"202": {
"description": "AttributeIP",
@@ -7019,7 +7019,7 @@
"tags": [
"databases"
],
- "description": "Update an ip attribute. Changing the `default` value will not update already existing documents.\n",
+ "description": "Update an ip attribute. Changing the `default` value will not update already existing documents.\r\n",
"responses": {
"200": {
"description": "AttributeIP",
@@ -7132,7 +7132,7 @@
"tags": [
"databases"
],
- "description": "Create relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\n",
+ "description": "Create relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\r\n",
"responses": {
"202": {
"description": "AttributeRelationship",
@@ -7265,7 +7265,7 @@
"tags": [
"databases"
],
- "description": "Create a string attribute.\n",
+ "description": "Create a string attribute.\r\n",
"responses": {
"202": {
"description": "AttributeString",
@@ -7384,7 +7384,7 @@
"tags": [
"databases"
],
- "description": "Update a string attribute. Changing the `default` value will not update already existing documents.\n",
+ "description": "Update a string attribute. Changing the `default` value will not update already existing documents.\r\n",
"responses": {
"200": {
"description": "AttributeString",
@@ -7502,7 +7502,7 @@
"tags": [
"databases"
],
- "description": "Create a URL attribute.\n",
+ "description": "Create a URL attribute.\r\n",
"responses": {
"202": {
"description": "AttributeURL",
@@ -7610,7 +7610,7 @@
"tags": [
"databases"
],
- "description": "Update an url attribute. Changing the `default` value will not update already existing documents.\n",
+ "description": "Update an url attribute. Changing the `default` value will not update already existing documents.\r\n",
"responses": {
"200": {
"description": "AttributeURL",
@@ -7909,7 +7909,7 @@
"tags": [
"databases"
],
- "description": "Update relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\n",
+ "description": "Update relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\r\n",
"responses": {
"200": {
"description": "AttributeRelationship",
@@ -8678,7 +8678,7 @@
"tags": [
"databases"
],
- "description": "Creates an index on the attributes listed. Your index should include all the attributes you will query in a single request.\nAttributes can be `key`, `fulltext`, and `unique`.",
+ "description": "Creates an index on the attributes listed. Your index should include all the attributes you will query in a single request.\r\nAttributes can be `key`, `fulltext`, and `unique`.",
"responses": {
"202": {
"description": "Index",
@@ -9658,7 +9658,7 @@
"tags": [
"functions"
],
- "description": "List allowed function specifications for this instance.\n",
+ "description": "List allowed function specifications for this instance.\r\n",
"responses": {
"200": {
"description": "Specifications List",
@@ -10373,7 +10373,7 @@
"tags": [
"functions"
],
- "description": "Create a new function code deployment. Use this endpoint to upload a new version of your code function. To execute your newly uploaded code, you'll need to update the function's deployment to use your new deployment UID.\n\nThis endpoint accepts a tar.gz file compressed with your code. Make sure to include any dependencies your code has within the compressed file. You can learn more about code packaging in the [Appwrite Cloud Functions tutorial](https:\/\/appwrite.io\/docs\/functions).\n\nUse the \"command\" param to set the entrypoint used to execute your code.",
+ "description": "Create a new function code deployment. Use this endpoint to upload a new version of your code function. To execute your newly uploaded code, you'll need to update the function's deployment to use your new deployment UID.\r\n\r\nThis endpoint accepts a tar.gz file compressed with your code. Make sure to include any dependencies your code has within the compressed file. You can learn more about code packaging in the [Appwrite Cloud Functions tutorial](https:\/\/appwrite.io\/docs\/functions).\r\n\r\nUse the \"command\" param to set the entrypoint used to execute your code.",
"responses": {
"202": {
"description": "Deployment",
@@ -11179,7 +11179,7 @@
"tags": [
"functions"
],
- "description": "Delete a function execution by its unique ID.\n",
+ "description": "Delete a function execution by its unique ID.\r\n",
"responses": {
"204": {
"description": "No content"
@@ -12453,7 +12453,7 @@
"tags": [
"health"
],
- "description": "Returns the amount of failed jobs in a given queue.\n",
+ "description": "Returns the amount of failed jobs in a given queue.\r\n",
"responses": {
"200": {
"description": "Health Queue",
@@ -13208,7 +13208,7 @@
"tags": [
"locale"
],
- "description": "Get the current user location based on IP. Returns an object with user country code, country name, continent name, continent code, ip address and suggested currency. You can use the locale header to get the data in a supported language.\n\n([IP Geolocation by DB-IP](https:\/\/db-ip.com))",
+ "description": "Get the current user location based on IP. Returns an object with user country code, country name, continent name, continent code, ip address and suggested currency. You can use the locale header to get the data in a supported language.\r\n\r\n([IP Geolocation by DB-IP](https:\/\/db-ip.com))",
"responses": {
"200": {
"description": "Locale",
@@ -13257,7 +13257,7 @@
},
"\/locale\/codes": {
"get": {
- "summary": "List Locale Codes",
+ "summary": "List locale codes",
"operationId": "localeListCodes",
"tags": [
"locale"
@@ -13864,7 +13864,7 @@
"tags": [
"messaging"
],
- "description": "Update an email message by its unique ID.\n",
+ "description": "Update an email message by its unique ID.\r\n",
"responses": {
"200": {
"description": "Message",
@@ -14169,7 +14169,7 @@
"tags": [
"messaging"
],
- "description": "Update a push notification by its unique ID.\n",
+ "description": "Update a push notification by its unique ID.\r\n",
"responses": {
"200": {
"description": "Message",
@@ -14439,7 +14439,7 @@
"tags": [
"messaging"
],
- "description": "Update an email message by its unique ID.\n",
+ "description": "Update an email message by its unique ID.\r\n",
"responses": {
"200": {
"description": "Message",
@@ -14553,7 +14553,7 @@
"tags": [
"messaging"
],
- "description": "Get a message by its unique ID.\n",
+ "description": "Get a message by its unique ID.\r\n",
"responses": {
"200": {
"description": "Message",
@@ -17029,7 +17029,7 @@
"tags": [
"messaging"
],
- "description": "Get a provider by its unique ID.\n",
+ "description": "Get a provider by its unique ID.\r\n",
"responses": {
"200": {
"description": "Provider",
@@ -17463,7 +17463,7 @@
"tags": [
"messaging"
],
- "description": "Get a topic by its unique ID.\n",
+ "description": "Get a topic by its unique ID.\r\n",
"responses": {
"200": {
"description": "Topic",
@@ -17525,7 +17525,7 @@
"tags": [
"messaging"
],
- "description": "Update a topic by its unique ID.\n",
+ "description": "Update a topic by its unique ID.\r\n",
"responses": {
"200": {
"description": "Topic",
@@ -17923,7 +17923,7 @@
"tags": [
"messaging"
],
- "description": "Get a subscriber by its unique ID.\n",
+ "description": "Get a subscriber by its unique ID.\r\n",
"responses": {
"200": {
"description": "Subscriber",
@@ -18060,7 +18060,7 @@
},
"\/migrations": {
"get": {
- "summary": "List Migrations",
+ "summary": "List migrations",
"operationId": "migrationsList",
"tags": [
"migrations"
@@ -18136,7 +18136,7 @@
},
"\/migrations\/appwrite": {
"post": {
- "summary": "Migrate Appwrite Data",
+ "summary": "Migrate Appwrite data",
"operationId": "migrationsCreateAppwriteMigration",
"tags": [
"migrations"
@@ -18226,7 +18226,7 @@
},
"\/migrations\/appwrite\/report": {
"get": {
- "summary": "Generate a report on Appwrite Data",
+ "summary": "Generate a report on Appwrite data",
"operationId": "migrationsGetAppwriteReport",
"tags": [
"migrations"
@@ -18321,7 +18321,7 @@
},
"\/migrations\/firebase": {
"post": {
- "summary": "Migrate Firebase Data (Service Account)",
+ "summary": "Migrate Firebase data (Service Account)",
"operationId": "migrationsCreateFirebaseMigration",
"tags": [
"migrations"
@@ -18399,7 +18399,7 @@
},
"\/migrations\/firebase\/deauthorize": {
"get": {
- "summary": "Revoke Appwrite's authorization to access Firebase Projects",
+ "summary": "Revoke Appwrite's authorization to access Firebase projects",
"operationId": "migrationsDeleteFirebaseAuth",
"tags": [
"migrations"
@@ -18442,7 +18442,7 @@
},
"\/migrations\/firebase\/oauth": {
"post": {
- "summary": "Migrate Firebase Data (OAuth)",
+ "summary": "Migrate Firebase data (OAuth)",
"operationId": "migrationsCreateFirebaseOAuthMigration",
"tags": [
"migrations"
@@ -18520,7 +18520,7 @@
},
"\/migrations\/firebase\/projects": {
"get": {
- "summary": "List Firebase Projects",
+ "summary": "List Firebase projects",
"operationId": "migrationsListFirebaseProjects",
"tags": [
"migrations"
@@ -18570,7 +18570,7 @@
},
"\/migrations\/firebase\/report": {
"get": {
- "summary": "Generate a report on Firebase Data",
+ "summary": "Generate a report on Firebase data",
"operationId": "migrationsGetFirebaseReport",
"tags": [
"migrations"
@@ -18644,7 +18644,7 @@
},
"\/migrations\/firebase\/report\/oauth": {
"get": {
- "summary": "Generate a report on Firebase Data using OAuth",
+ "summary": "Generate a report on Firebase data using OAuth",
"operationId": "migrationsGetFirebaseReportOAuth",
"tags": [
"migrations"
@@ -18718,7 +18718,7 @@
},
"\/migrations\/nhost": {
"post": {
- "summary": "Migrate NHost Data",
+ "summary": "Migrate NHost data",
"operationId": "migrationsCreateNHostMigration",
"tags": [
"migrations"
@@ -18966,7 +18966,7 @@
},
"\/migrations\/supabase": {
"post": {
- "summary": "Migrate Supabase Data",
+ "summary": "Migrate Supabase data",
"operationId": "migrationsCreateSupabaseMigration",
"tags": [
"migrations"
@@ -19199,7 +19199,7 @@
},
"\/migrations\/{migrationId}": {
"get": {
- "summary": "Get Migration",
+ "summary": "Get migration",
"operationId": "migrationsGet",
"tags": [
"migrations"
@@ -19259,7 +19259,7 @@
]
},
"patch": {
- "summary": "Retry Migration",
+ "summary": "Retry migration",
"operationId": "migrationsRetry",
"tags": [
"migrations"
@@ -19319,7 +19319,7 @@
]
},
"delete": {
- "summary": "Delete Migration",
+ "summary": "Delete migration",
"operationId": "migrationsDelete",
"tags": [
"migrations"
@@ -19464,7 +19464,7 @@
},
"\/project\/variables": {
"get": {
- "summary": "List Variables",
+ "summary": "List variables",
"operationId": "projectListVariables",
"tags": [
"project"
@@ -19512,7 +19512,7 @@
]
},
"post": {
- "summary": "Create Variable",
+ "summary": "Create variable",
"operationId": "projectCreateVariable",
"tags": [
"project"
@@ -19587,7 +19587,7 @@
},
"\/project\/variables\/{variableId}": {
"get": {
- "summary": "Get Variable",
+ "summary": "Get variable",
"operationId": "projectGetVariable",
"tags": [
"project"
@@ -19647,7 +19647,7 @@
]
},
"put": {
- "summary": "Update Variable",
+ "summary": "Update variable",
"operationId": "projectUpdateVariable",
"tags": [
"project"
@@ -19731,7 +19731,7 @@
}
},
"delete": {
- "summary": "Delete Variable",
+ "summary": "Delete variable",
"operationId": "projectDeleteVariable",
"tags": [
"project"
@@ -21282,7 +21282,7 @@
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
- "scope": "projects.read",
+ "scope": "keys.read",
"platforms": [
"console"
],
@@ -21342,7 +21342,7 @@
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
- "scope": "projects.write",
+ "scope": "keys.write",
"platforms": [
"console"
],
@@ -21437,7 +21437,7 @@
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
- "scope": "projects.read",
+ "scope": "keys.read",
"platforms": [
"console"
],
@@ -21507,7 +21507,7 @@
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
- "scope": "projects.write",
+ "scope": "keys.write",
"platforms": [
"console"
],
@@ -21603,7 +21603,7 @@
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
- "scope": "projects.write",
+ "scope": "keys.write",
"platforms": [
"console"
],
@@ -21814,7 +21814,7 @@
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
- "scope": "projects.read",
+ "scope": "platforms.read",
"platforms": [
"console"
],
@@ -21874,7 +21874,7 @@
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
- "scope": "projects.write",
+ "scope": "platforms.write",
"platforms": [
"console"
],
@@ -21995,7 +21995,7 @@
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
- "scope": "projects.read",
+ "scope": "platforms.read",
"platforms": [
"console"
],
@@ -22065,7 +22065,7 @@
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
- "scope": "projects.write",
+ "scope": "platforms.write",
"platforms": [
"console"
],
@@ -22162,7 +22162,7 @@
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
- "scope": "projects.write",
+ "scope": "platforms.write",
"platforms": [
"console"
],
@@ -22620,7 +22620,8 @@
"description": "Does SMTP server use secure connection",
"x-example": "tls",
"enum": [
- "tls"
+ "tls",
+ "ssl"
],
"x-enum-name": "SMTPSecure",
"x-enum-keys": []
@@ -24640,7 +24641,7 @@
},
"\/proxy\/rules": {
"get": {
- "summary": "List Rules",
+ "summary": "List rules",
"operationId": "proxyListRules",
"tags": [
"proxy"
@@ -24714,7 +24715,7 @@
]
},
"post": {
- "summary": "Create Rule",
+ "summary": "Create rule",
"operationId": "proxyCreateRule",
"tags": [
"proxy"
@@ -24800,7 +24801,7 @@
},
"\/proxy\/rules\/{ruleId}": {
"get": {
- "summary": "Get Rule",
+ "summary": "Get rule",
"operationId": "proxyGetRule",
"tags": [
"proxy"
@@ -24860,7 +24861,7 @@
]
},
"delete": {
- "summary": "Delete Rule",
+ "summary": "Delete rule",
"operationId": "proxyDeleteRule",
"tags": [
"proxy"
@@ -24915,7 +24916,7 @@
},
"\/proxy\/rules\/{ruleId}\/verification": {
"patch": {
- "summary": "Update Rule Verification Status",
+ "summary": "Update rule verification status",
"operationId": "proxyUpdateRuleVerification",
"tags": [
"proxy"
@@ -25524,7 +25525,7 @@
"tags": [
"storage"
],
- "description": "Create a new file. Before using this route, you should create a new bucket resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/storage#storageCreateBucket) API or directly from your Appwrite console.\n\nLarger files should be uploaded using multiple requests with the [content-range](https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Content-Range) header to send a partial request with a maximum supported chunk of `5MB`. The `content-range` header values should always be in bytes.\n\nWhen the first request is sent, the server will return the **File** object, and the subsequent part request must include the file's **id** in `x-appwrite-id` header to allow the server to know that the partial upload is for the existing file and not for a new one.\n\nIf you're creating a new file using one of the Appwrite SDKs, all the chunking logic will be managed by the SDK internally.\n",
+ "description": "Create a new file. Before using this route, you should create a new bucket resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/storage#storageCreateBucket) API or directly from your Appwrite console.\r\n\r\nLarger files should be uploaded using multiple requests with the [content-range](https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Content-Range) header to send a partial request with a maximum supported chunk of `5MB`. The `content-range` header values should always be in bytes.\r\n\r\nWhen the first request is sent, the server will return the **File** object, and the subsequent part request must include the file's **id** in `x-appwrite-id` header to allow the server to know that the partial upload is for the existing file and not for a new one.\r\n\r\nIf you're creating a new file using one of the Appwrite SDKs, all the chunking logic will be managed by the SDK internally.\r\n",
"responses": {
"201": {
"description": "File",
@@ -25791,7 +25792,7 @@
}
},
"delete": {
- "summary": "Delete File",
+ "summary": "Delete file",
"operationId": "storageDeleteFile",
"tags": [
"storage"
@@ -26133,7 +26134,8 @@
"jpeg",
"gif",
"png",
- "webp"
+ "webp",
+ "avif"
],
"x-enum-name": "ImageFormat",
"x-enum-keys": [],
@@ -26912,7 +26914,7 @@
"tags": [
"teams"
],
- "description": "Invite a new member to join your team. Provide an ID for existing users, or invite unregistered users using an email or phone number. If initiated from a Client SDK, Appwrite will send an email or sms with a link to join the team to the invited user, and an account will be created for them if one doesn't exist. If initiated from a Server SDK, the new member will be added automatically to the team.\n\nYou only need to provide one of a user ID, email, or phone number. Appwrite will prioritize accepting the user ID > email > phone number if you provide more than one of these parameters.\n\nUse the `url` parameter to redirect the user from the invitation email to your app. After the user is redirected, use the [Update Team Membership Status](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/teams#updateMembershipStatus) endpoint to allow the user to accept the invitation to the team. \n\nPlease note that to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) Appwrite will accept the only redirect URLs under the domains you have added as a platform on the Appwrite Console.\n",
+ "description": "Invite a new member to join your team. Provide an ID for existing users, or invite unregistered users using an email or phone number. If initiated from a Client SDK, Appwrite will send an email or sms with a link to join the team to the invited user, and an account will be created for them if one doesn't exist. If initiated from a Server SDK, the new member will be added automatically to the team.\r\n\r\nYou only need to provide one of a user ID, email, or phone number. Appwrite will prioritize accepting the user ID > email > phone number if you provide more than one of these parameters.\r\n\r\nUse the `url` parameter to redirect the user from the invitation email to your app. After the user is redirected, use the [Update Team Membership Status](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/teams#updateMembershipStatus) endpoint to allow the user to accept the invitation to the team. \r\n\r\nPlease note that to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) Appwrite will accept the only redirect URLs under the domains you have added as a platform on the Appwrite Console.\r\n",
"responses": {
"201": {
"description": "Membership",
@@ -27099,7 +27101,7 @@
"tags": [
"teams"
],
- "description": "Modify the roles of a team member. Only team members with the owner role have access to this endpoint. Learn more about [roles and permissions](https:\/\/appwrite.io\/docs\/permissions).\n",
+ "description": "Modify the roles of a team member. Only team members with the owner role have access to this endpoint. Learn more about [roles and permissions](https:\/\/appwrite.io\/docs\/permissions).\r\n",
"responses": {
"200": {
"description": "Membership",
@@ -27264,7 +27266,7 @@
"tags": [
"teams"
],
- "description": "Use this endpoint to allow a user to accept an invitation to join a team after being redirected back to your app from the invitation email received by the user.\n\nIf the request is successful, a session for the user is automatically created.\n",
+ "description": "Use this endpoint to allow a user to accept an invitation to join a team after being redirected back to your app from the invitation email received by the user.\r\n\r\nIf the request is successful, a session for the user is automatically created.\r\n",
"responses": {
"200": {
"description": "Membership",
@@ -27842,7 +27844,7 @@
},
"\/users\/identities": {
"get": {
- "summary": "List Identities",
+ "summary": "List identities",
"operationId": "usersListIdentities",
"tags": [
"users"
@@ -28840,7 +28842,7 @@
"tags": [
"users"
],
- "description": "Update the user labels by its unique ID. \n\nLabels can be used to grant access to resources. While teams are a way for user's to share access to a resource, labels can be defined by the developer to grant access without an invitation. See the [Permissions docs](https:\/\/appwrite.io\/docs\/permissions) for more info.",
+ "description": "Update the user labels by its unique ID. \r\n\r\nLabels can be used to grant access to resources. While teams are a way for user's to share access to a resource, labels can be defined by the developer to grant access without an invitation. See the [Permissions docs](https:\/\/appwrite.io\/docs\/permissions) for more info.",
"responses": {
"200": {
"description": "User",
@@ -29141,7 +29143,7 @@
},
"\/users\/{userId}\/mfa\/authenticators\/{type}": {
"delete": {
- "summary": "Delete Authenticator",
+ "summary": "Delete authenticator",
"operationId": "usersDeleteMfaAuthenticator",
"tags": [
"users"
@@ -29219,7 +29221,7 @@
},
"\/users\/{userId}\/mfa\/factors": {
"get": {
- "summary": "List Factors",
+ "summary": "List factors",
"operationId": "usersListMfaFactors",
"tags": [
"users"
@@ -29282,7 +29284,7 @@
},
"\/users\/{userId}\/mfa\/recovery-codes": {
"get": {
- "summary": "Get MFA Recovery Codes",
+ "summary": "Get MFA recovery codes",
"operationId": "usersGetMfaRecoveryCodes",
"tags": [
"users"
@@ -29343,7 +29345,7 @@
]
},
"put": {
- "summary": "Regenerate MFA Recovery Codes",
+ "summary": "Regenerate MFA recovery codes",
"operationId": "usersUpdateMfaRecoveryCodes",
"tags": [
"users"
@@ -29404,7 +29406,7 @@
]
},
"patch": {
- "summary": "Create MFA Recovery Codes",
+ "summary": "Create MFA recovery codes",
"operationId": "usersCreateMfaRecoveryCodes",
"tags": [
"users"
@@ -29922,7 +29924,7 @@
"tags": [
"users"
],
- "description": "Creates a session for a user. Returns an immediately usable session object.\n\nIf you want to generate a token for a custom authentication flow, use the [POST \/users\/{userId}\/tokens](https:\/\/appwrite.io\/docs\/server\/users#createToken) endpoint.",
+ "description": "Creates a session for a user. Returns an immediately usable session object.\r\n\r\nIf you want to generate a token for a custom authentication flow, use the [POST \/users\/{userId}\/tokens](https:\/\/appwrite.io\/docs\/server\/users#createToken) endpoint.",
"responses": {
"201": {
"description": "Session",
@@ -30182,7 +30184,7 @@
},
"\/users\/{userId}\/targets": {
"get": {
- "summary": "List User Targets",
+ "summary": "List user targets",
"operationId": "usersListTargets",
"tags": [
"users"
@@ -30257,7 +30259,7 @@
]
},
"post": {
- "summary": "Create User Target",
+ "summary": "Create user target",
"operationId": "usersCreateTarget",
"tags": [
"users"
@@ -30369,7 +30371,7 @@
},
"\/users\/{userId}\/targets\/{targetId}": {
"get": {
- "summary": "Get User Target",
+ "summary": "Get user target",
"operationId": "usersGetTarget",
"tags": [
"users"
@@ -30441,7 +30443,7 @@
]
},
"patch": {
- "summary": "Update User target",
+ "summary": "Update user target",
"operationId": "usersUpdateTarget",
"tags": [
"users"
@@ -30611,7 +30613,7 @@
"tags": [
"users"
],
- "description": "Returns a token with a secret key for creating a session. Use the user ID and secret and submit a request to the [PUT \/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process.\n",
+ "description": "Returns a token with a secret key for creating a session. Use the user ID and secret and submit a request to the [PUT \/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process.\r\n",
"responses": {
"201": {
"description": "Token",
@@ -30854,7 +30856,7 @@
},
"\/vcs\/github\/installations\/{installationId}\/providerRepositories": {
"get": {
- "summary": "List Repositories",
+ "summary": "List repositories",
"operationId": "vcsListRepositories",
"tags": [
"vcs"
@@ -31084,7 +31086,7 @@
},
"\/vcs\/github\/installations\/{installationId}\/providerRepositories\/{providerRepositoryId}\/branches": {
"get": {
- "summary": "List Repository Branches",
+ "summary": "List repository branches",
"operationId": "vcsListRepositoryBranches",
"tags": [
"vcs"
@@ -31547,7 +31549,7 @@
]
},
"delete": {
- "summary": "Delete Installation",
+ "summary": "Delete installation",
"operationId": "vcsDeleteInstallation",
"tags": [
"vcs"
@@ -32946,6 +32948,16 @@
"x-example": false,
"nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"size": {
"type": "integer",
"description": "Attribute size.",
@@ -32965,6 +32977,8 @@
"status",
"error",
"required",
+ "$createdAt",
+ "$updatedAt",
"size"
]
},
@@ -33003,6 +33017,16 @@
"x-example": false,
"nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"min": {
"type": "integer",
"description": "Minimum value to enforce for new documents.",
@@ -33030,7 +33054,9 @@
"type",
"status",
"error",
- "required"
+ "required",
+ "$createdAt",
+ "$updatedAt"
]
},
"attributeFloat": {
@@ -33068,6 +33094,16 @@
"x-example": false,
"nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"min": {
"type": "number",
"description": "Minimum value to enforce for new documents.",
@@ -33095,7 +33131,9 @@
"type",
"status",
"error",
- "required"
+ "required",
+ "$createdAt",
+ "$updatedAt"
]
},
"attributeBoolean": {
@@ -33133,6 +33171,16 @@
"x-example": false,
"nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"default": {
"type": "boolean",
"description": "Default value for attribute when not provided. Cannot be set when attribute is required.",
@@ -33145,7 +33193,9 @@
"type",
"status",
"error",
- "required"
+ "required",
+ "$createdAt",
+ "$updatedAt"
]
},
"attributeEmail": {
@@ -33183,6 +33233,16 @@
"x-example": false,
"nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"format": {
"type": "string",
"description": "String format.",
@@ -33201,6 +33261,8 @@
"status",
"error",
"required",
+ "$createdAt",
+ "$updatedAt",
"format"
]
},
@@ -33239,6 +33301,16 @@
"x-example": false,
"nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"elements": {
"type": "array",
"description": "Array of elements in enumerated type.",
@@ -33265,6 +33337,8 @@
"status",
"error",
"required",
+ "$createdAt",
+ "$updatedAt",
"elements",
"format"
]
@@ -33304,6 +33378,16 @@
"x-example": false,
"nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"format": {
"type": "string",
"description": "String format.",
@@ -33322,6 +33406,8 @@
"status",
"error",
"required",
+ "$createdAt",
+ "$updatedAt",
"format"
]
},
@@ -33360,6 +33446,16 @@
"x-example": false,
"nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"format": {
"type": "string",
"description": "String format.",
@@ -33378,6 +33474,8 @@
"status",
"error",
"required",
+ "$createdAt",
+ "$updatedAt",
"format"
]
},
@@ -33416,6 +33514,16 @@
"x-example": false,
"nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"format": {
"type": "string",
"description": "ISO 8601 format.",
@@ -33434,6 +33542,8 @@
"status",
"error",
"required",
+ "$createdAt",
+ "$updatedAt",
"format"
]
},
@@ -33472,6 +33582,16 @@
"x-example": false,
"nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"relatedCollection": {
"type": "string",
"description": "The ID of the related collection.",
@@ -33509,6 +33629,8 @@
"status",
"error",
"required",
+ "$createdAt",
+ "$updatedAt",
"relatedCollection",
"relationType",
"twoWay",
@@ -33557,6 +33679,16 @@
},
"x-example": [],
"nullable": true
+ },
+ "$createdAt": {
+ "type": "string",
+ "description": "Index creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Index update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
}
},
"required": [
@@ -33564,7 +33696,9 @@
"type",
"status",
"error",
- "attributes"
+ "attributes",
+ "$createdAt",
+ "$updatedAt"
]
},
"document": {
@@ -35589,7 +35723,7 @@
"responseBody": {
"type": "string",
"description": "HTTP response body. This will return empty unless execution is created as synchronous.",
- "x-example": "Developers are awesome."
+ "x-example": ""
},
"responseHeaders": {
"type": "array",
@@ -36747,6 +36881,12 @@
"x-example": 0,
"format": "int32"
},
+ "storageTotal": {
+ "type": "integer",
+ "description": "Total aggregated number of total databases storage in bytes.",
+ "x-example": 0,
+ "format": "int32"
+ },
"databases": {
"type": "array",
"description": "Aggregated number of databases per period.",
@@ -36770,6 +36910,14 @@
"$ref": "#\/components\/schemas\/metric"
},
"x-example": []
+ },
+ "storage": {
+ "type": "array",
+ "description": "An array of the aggregated number of databases storage in bytes per period.",
+ "items": {
+ "$ref": "#\/components\/schemas\/metric"
+ },
+ "x-example": []
}
},
"required": [
@@ -36777,9 +36925,11 @@
"databasesTotal",
"collectionsTotal",
"documentsTotal",
+ "storageTotal",
"databases",
"collections",
- "documents"
+ "documents",
+ "storage"
]
},
"usageDatabase": {
@@ -36803,6 +36953,12 @@
"x-example": 0,
"format": "int32"
},
+ "storageTotal": {
+ "type": "integer",
+ "description": "Total aggregated number of total storage used in bytes.",
+ "x-example": 0,
+ "format": "int32"
+ },
"collections": {
"type": "array",
"description": "Aggregated number of collections per period.",
@@ -36818,14 +36974,24 @@
"$ref": "#\/components\/schemas\/metric"
},
"x-example": []
+ },
+ "storage": {
+ "type": "array",
+ "description": "Aggregated storage used in bytes per period.",
+ "items": {
+ "$ref": "#\/components\/schemas\/metric"
+ },
+ "x-example": []
}
},
"required": [
"range",
"collectionsTotal",
"documentsTotal",
+ "storageTotal",
"collections",
- "documents"
+ "documents",
+ "storage"
]
},
"usageCollection": {
@@ -37366,6 +37532,12 @@
"x-example": 0,
"format": "int32"
},
+ "databasesStorageTotal": {
+ "type": "integer",
+ "description": "Total aggregated sum of databases storage size (in bytes).",
+ "x-example": 0,
+ "format": "int32"
+ },
"usersTotal": {
"type": "integer",
"description": "Total aggregated number of users.",
@@ -37462,6 +37634,14 @@
},
"x-example": []
},
+ "databasesStorageBreakdown": {
+ "type": "array",
+ "description": "An array of the aggregated breakdown of storage usage by databases.",
+ "items": {
+ "$ref": "#\/components\/schemas\/metricBreakdown"
+ },
+ "x-example": []
+ },
"executionsMbSecondsBreakdown": {
"type": "array",
"description": "Aggregated breakdown in totals of execution mbSeconds by functions.",
@@ -37491,6 +37671,7 @@
"executionsTotal",
"documentsTotal",
"databasesTotal",
+ "databasesStorageTotal",
"usersTotal",
"filesStorageTotal",
"functionsStorageTotal",
@@ -37505,6 +37686,7 @@
"executions",
"executionsBreakdown",
"bucketsBreakdown",
+ "databasesStorageBreakdown",
"executionsMbSecondsBreakdown",
"buildsMbSecondsBreakdown",
"functionsStorageBreakdown"
diff --git a/app/config/specs/open-api3-latest-server.json b/app/config/specs/open-api3-latest-server.json
index ef41688ca8..274eac9686 100644
--- a/app/config/specs/open-api3-latest-server.json
+++ b/app/config/specs/open-api3-latest-server.json
@@ -167,7 +167,7 @@
"tags": [
"account"
],
- "description": "Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. For security measures, user password is required to complete this request.\nThis endpoint can also be used to convert an anonymous account to a normal one, by passing an email address and a new password.\n",
+ "description": "Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. For security measures, user password is required to complete this request.\r\nThis endpoint can also be used to convert an anonymous account to a normal one, by passing an email address and a new password.\r\n",
"responses": {
"200": {
"description": "User",
@@ -241,7 +241,7 @@
},
"\/account\/identities": {
"get": {
- "summary": "List Identities",
+ "summary": "List identities",
"operationId": "accountListIdentities",
"tags": [
"account"
@@ -562,7 +562,7 @@
},
"\/account\/mfa\/authenticators\/{type}": {
"post": {
- "summary": "Create Authenticator",
+ "summary": "Create authenticator",
"operationId": "accountCreateMfaAuthenticator",
"tags": [
"account"
@@ -631,7 +631,7 @@
]
},
"put": {
- "summary": "Verify Authenticator",
+ "summary": "Verify authenticator",
"operationId": "accountUpdateMfaAuthenticator",
"tags": [
"account"
@@ -719,7 +719,7 @@
}
},
"delete": {
- "summary": "Delete Authenticator",
+ "summary": "Delete authenticator",
"operationId": "accountDeleteMfaAuthenticator",
"tags": [
"account"
@@ -783,7 +783,7 @@
},
"\/account\/mfa\/challenge": {
"post": {
- "summary": "Create MFA Challenge",
+ "summary": "Create MFA challenge",
"operationId": "accountCreateMfaChallenge",
"tags": [
"account"
@@ -859,7 +859,7 @@
}
},
"put": {
- "summary": "Create MFA Challenge (confirmation)",
+ "summary": "Create MFA challenge (confirmation)",
"operationId": "accountUpdateMfaChallenge",
"tags": [
"account"
@@ -938,7 +938,7 @@
},
"\/account\/mfa\/factors": {
"get": {
- "summary": "List Factors",
+ "summary": "List factors",
"operationId": "accountListMfaFactors",
"tags": [
"account"
@@ -992,7 +992,7 @@
},
"\/account\/mfa\/recovery-codes": {
"get": {
- "summary": "Get MFA Recovery Codes",
+ "summary": "Get MFA recovery codes",
"operationId": "accountGetMfaRecoveryCodes",
"tags": [
"account"
@@ -1044,7 +1044,7 @@
]
},
"post": {
- "summary": "Create MFA Recovery Codes",
+ "summary": "Create MFA recovery codes",
"operationId": "accountCreateMfaRecoveryCodes",
"tags": [
"account"
@@ -1096,7 +1096,7 @@
]
},
"patch": {
- "summary": "Regenerate MFA Recovery Codes",
+ "summary": "Regenerate MFA recovery codes",
"operationId": "accountUpdateMfaRecoveryCodes",
"tags": [
"account"
@@ -1590,7 +1590,7 @@
"tags": [
"account"
],
- "description": "Use this endpoint to complete the user account password reset. Both the **userId** and **secret** arguments will be passed as query parameters to the redirect URL you have provided when sending your request to the [POST \/account\/recovery](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createRecovery) endpoint.\n\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.",
+ "description": "Use this endpoint to complete the user account password reset. Both the **userId** and **secret** arguments will be passed as query parameters to the redirect URL you have provided when sending your request to the [POST \/account\/recovery](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createRecovery) endpoint.\r\n\r\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.",
"responses": {
"200": {
"description": "Token",
@@ -1825,7 +1825,7 @@
"tags": [
"account"
],
- "description": "Allow the user to login into their account by providing a valid email and password combination. This route will create a new session for the user.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
+ "description": "Allow the user to login into their account by providing a valid email and password combination. This route will create a new session for the user.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
"responses": {
"201": {
"description": "Session",
@@ -2370,7 +2370,7 @@
"tags": [
"account"
],
- "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's email is valid for 15 minutes.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
+ "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's email is valid for 15 minutes.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
"responses": {
"201": {
"description": "Token",
@@ -2451,7 +2451,7 @@
"tags": [
"account"
],
- "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour. If you are on a mobile device you can leave the URL parameter empty, so that the login completion will be handled by your Appwrite instance by default.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\n",
+ "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour. If you are on a mobile device you can leave the URL parameter empty, so that the login completion will be handled by your Appwrite instance by default.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\r\n",
"responses": {
"201": {
"description": "Token",
@@ -2540,7 +2540,7 @@
"tags": [
"account"
],
- "description": "Allow the user to login to their account using the OAuth2 provider of their choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed. \n\nIf authentication succeeds, `userId` and `secret` of a token will be appended to the success URL as query parameters. These can be used to create a new session using the [Create session](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
+ "description": "Allow the user to login to their account using the OAuth2 provider of their choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed. \r\n\r\nIf authentication succeeds, `userId` and `secret` of a token will be appended to the success URL as query parameters. These can be used to create a new session using the [Create session](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
"responses": {
"301": {
"description": "File"
@@ -2676,7 +2676,7 @@
"tags": [
"account"
],
- "description": "Sends the user an SMS with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's phone is valid for 15 minutes.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
+ "description": "Sends the user an SMS with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's phone is valid for 15 minutes.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
"responses": {
"201": {
"description": "Token",
@@ -2755,7 +2755,7 @@
"tags": [
"account"
],
- "description": "Use this endpoint to send a verification message to your user email address to confirm they are the valid owners of that address. Both the **userId** and **secret** arguments will be passed as query parameters to the URL you have provided to be attached to the verification email. The provided URL should redirect the user back to your app and allow you to complete the verification process by verifying both the **userId** and **secret** parameters. Learn more about how to [complete the verification process](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\n\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.\n",
+ "description": "Use this endpoint to send a verification message to your user email address to confirm they are the valid owners of that address. Both the **userId** and **secret** arguments will be passed as query parameters to the URL you have provided to be attached to the verification email. The provided URL should redirect the user back to your app and allow you to complete the verification process by verifying both the **userId** and **secret** parameters. Learn more about how to [complete the verification process](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\r\n\r\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.\r\n",
"responses": {
"201": {
"description": "Token",
@@ -3039,7 +3039,7 @@
"tags": [
"avatars"
],
- "description": "You can use this endpoint to show different browser icons to your users. The code argument receives the browser code as it appears in your user [GET \/account\/sessions](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#getSessions) endpoint. Use width, height and quality arguments to change the output settings.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.",
+ "description": "You can use this endpoint to show different browser icons to your users. The code argument receives the browser code as it appears in your user [GET \/account\/sessions](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#getSessions) endpoint. Use width, height and quality arguments to change the output settings.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.",
"responses": {
"200": {
"description": "Image"
@@ -3169,7 +3169,7 @@
"tags": [
"avatars"
],
- "description": "The credit card endpoint will return you the icon of the credit card provider you need. Use width, height and quality arguments to change the output settings.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n",
+ "description": "The credit card endpoint will return you the icon of the credit card provider you need. Use width, height and quality arguments to change the output settings.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n",
"responses": {
"200": {
"description": "Image"
@@ -3303,7 +3303,7 @@
"tags": [
"avatars"
],
- "description": "Use this endpoint to fetch the favorite icon (AKA favicon) of any remote website URL.\n\nThis endpoint does not follow HTTP redirects.",
+ "description": "Use this endpoint to fetch the favorite icon (AKA favicon) of any remote website URL.\r\n\r\nThis endpoint does not follow HTTP redirects.",
"responses": {
"200": {
"description": "Image"
@@ -3365,7 +3365,7 @@
"tags": [
"avatars"
],
- "description": "You can use this endpoint to show different country flags icons to your users. The code argument receives the 2 letter country code. Use width, height and quality arguments to change the output settings. Country codes follow the [ISO 3166-1](https:\/\/en.wikipedia.org\/wiki\/ISO_3166-1) standard.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n",
+ "description": "You can use this endpoint to show different country flags icons to your users. The code argument receives the 2 letter country code. Use width, height and quality arguments to change the output settings. Country codes follow the [ISO 3166-1](https:\/\/en.wikipedia.org\/wiki\/ISO_3166-1) standard.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n",
"responses": {
"200": {
"description": "Image"
@@ -3857,7 +3857,7 @@
"tags": [
"avatars"
],
- "description": "Use this endpoint to fetch a remote image URL and crop it to any image size you want. This endpoint is very useful if you need to crop and display remote images in your app or in case you want to make sure a 3rd party image is properly served using a TLS protocol.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 400x400px.\n\nThis endpoint does not follow HTTP redirects.",
+ "description": "Use this endpoint to fetch a remote image URL and crop it to any image size you want. This endpoint is very useful if you need to crop and display remote images in your app or in case you want to make sure a 3rd party image is properly served using a TLS protocol.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 400x400px.\r\n\r\nThis endpoint does not follow HTTP redirects.",
"responses": {
"200": {
"description": "Image"
@@ -3943,7 +3943,7 @@
"tags": [
"avatars"
],
- "description": "Use this endpoint to show your user initials avatar icon on your website or app. By default, this route will try to print your logged-in user name or email initials. You can also overwrite the user name if you pass the 'name' parameter. If no name is given and no user is logged, an empty avatar will be returned.\n\nYou can use the color and background params to change the avatar colors. By default, a random theme will be selected. The random theme will persist for the user's initials when reloading the same theme will always return for the same initials.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n",
+ "description": "Use this endpoint to show your user initials avatar icon on your website or app. By default, this route will try to print your logged-in user name or email initials. You can also overwrite the user name if you pass the 'name' parameter. If no name is given and no user is logged, an empty avatar will be returned.\r\n\r\nYou can use the color and background params to change the avatar colors. By default, a random theme will be selected. The random theme will persist for the user's initials when reloading the same theme will always return for the same initials.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n",
"responses": {
"200": {
"description": "Image"
@@ -4039,7 +4039,7 @@
"tags": [
"avatars"
],
- "description": "Converts a given plain text to a QR code image. You can use the query parameters to change the size and style of the resulting image.\n",
+ "description": "Converts a given plain text to a QR code image. You can use the query parameters to change the size and style of the resulting image.\r\n",
"responses": {
"200": {
"description": "Image"
@@ -4211,7 +4211,7 @@
"tags": [
"databases"
],
- "description": "Create a new Database.\n",
+ "description": "Create a new Database.\r\n",
"responses": {
"201": {
"description": "Database",
@@ -5026,7 +5026,7 @@
"tags": [
"databases"
],
- "description": "Create a boolean attribute.\n",
+ "description": "Create a boolean attribute.\r\n",
"responses": {
"202": {
"description": "AttributeBoolean",
@@ -5472,7 +5472,7 @@
"tags": [
"databases"
],
- "description": "Create an email attribute.\n",
+ "description": "Create an email attribute.\r\n",
"responses": {
"202": {
"description": "AttributeEmail",
@@ -5581,7 +5581,7 @@
"tags": [
"databases"
],
- "description": "Update an email attribute. Changing the `default` value will not update already existing documents.\n",
+ "description": "Update an email attribute. Changing the `default` value will not update already existing documents.\r\n",
"responses": {
"200": {
"description": "AttributeEmail",
@@ -5695,7 +5695,7 @@
"tags": [
"databases"
],
- "description": "Create an enumeration attribute. The `elements` param acts as a white-list of accepted values for this attribute. \n",
+ "description": "Create an enumeration attribute. The `elements` param acts as a white-list of accepted values for this attribute. \r\n",
"responses": {
"202": {
"description": "AttributeEnum",
@@ -5813,7 +5813,7 @@
"tags": [
"databases"
],
- "description": "Update an enum attribute. Changing the `default` value will not update already existing documents.\n",
+ "description": "Update an enum attribute. Changing the `default` value will not update already existing documents.\r\n",
"responses": {
"200": {
"description": "AttributeEnum",
@@ -5936,7 +5936,7 @@
"tags": [
"databases"
],
- "description": "Create a float attribute. Optionally, minimum and maximum values can be provided.\n",
+ "description": "Create a float attribute. Optionally, minimum and maximum values can be provided.\r\n",
"responses": {
"202": {
"description": "AttributeFloat",
@@ -6055,7 +6055,7 @@
"tags": [
"databases"
],
- "description": "Update a float attribute. Changing the `default` value will not update already existing documents.\n",
+ "description": "Update a float attribute. Changing the `default` value will not update already existing documents.\r\n",
"responses": {
"200": {
"description": "AttributeFloat",
@@ -6181,7 +6181,7 @@
"tags": [
"databases"
],
- "description": "Create an integer attribute. Optionally, minimum and maximum values can be provided.\n",
+ "description": "Create an integer attribute. Optionally, minimum and maximum values can be provided.\r\n",
"responses": {
"202": {
"description": "AttributeInteger",
@@ -6300,7 +6300,7 @@
"tags": [
"databases"
],
- "description": "Update an integer attribute. Changing the `default` value will not update already existing documents.\n",
+ "description": "Update an integer attribute. Changing the `default` value will not update already existing documents.\r\n",
"responses": {
"200": {
"description": "AttributeInteger",
@@ -6426,7 +6426,7 @@
"tags": [
"databases"
],
- "description": "Create IP address attribute.\n",
+ "description": "Create IP address attribute.\r\n",
"responses": {
"202": {
"description": "AttributeIP",
@@ -6535,7 +6535,7 @@
"tags": [
"databases"
],
- "description": "Update an ip attribute. Changing the `default` value will not update already existing documents.\n",
+ "description": "Update an ip attribute. Changing the `default` value will not update already existing documents.\r\n",
"responses": {
"200": {
"description": "AttributeIP",
@@ -6649,7 +6649,7 @@
"tags": [
"databases"
],
- "description": "Create relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\n",
+ "description": "Create relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\r\n",
"responses": {
"202": {
"description": "AttributeRelationship",
@@ -6783,7 +6783,7 @@
"tags": [
"databases"
],
- "description": "Create a string attribute.\n",
+ "description": "Create a string attribute.\r\n",
"responses": {
"202": {
"description": "AttributeString",
@@ -6903,7 +6903,7 @@
"tags": [
"databases"
],
- "description": "Update a string attribute. Changing the `default` value will not update already existing documents.\n",
+ "description": "Update a string attribute. Changing the `default` value will not update already existing documents.\r\n",
"responses": {
"200": {
"description": "AttributeString",
@@ -7022,7 +7022,7 @@
"tags": [
"databases"
],
- "description": "Create a URL attribute.\n",
+ "description": "Create a URL attribute.\r\n",
"responses": {
"202": {
"description": "AttributeURL",
@@ -7131,7 +7131,7 @@
"tags": [
"databases"
],
- "description": "Update an url attribute. Changing the `default` value will not update already existing documents.\n",
+ "description": "Update an url attribute. Changing the `default` value will not update already existing documents.\r\n",
"responses": {
"200": {
"description": "AttributeURL",
@@ -7433,7 +7433,7 @@
"tags": [
"databases"
],
- "description": "Update relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\n",
+ "description": "Update relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\r\n",
"responses": {
"200": {
"description": "AttributeRelationship",
@@ -8119,7 +8119,7 @@
"tags": [
"databases"
],
- "description": "Creates an index on the attributes listed. Your index should include all the attributes you will query in a single request.\nAttributes can be `key`, `fulltext`, and `unique`.",
+ "description": "Creates an index on the attributes listed. Your index should include all the attributes you will query in a single request.\r\nAttributes can be `key`, `fulltext`, and `unique`.",
"responses": {
"202": {
"description": "Index",
@@ -8767,7 +8767,7 @@
"tags": [
"functions"
],
- "description": "List allowed function specifications for this instance.\n",
+ "description": "List allowed function specifications for this instance.\r\n",
"responses": {
"200": {
"description": "Specifications List",
@@ -9249,7 +9249,7 @@
"tags": [
"functions"
],
- "description": "Create a new function code deployment. Use this endpoint to upload a new version of your code function. To execute your newly uploaded code, you'll need to update the function's deployment to use your new deployment UID.\n\nThis endpoint accepts a tar.gz file compressed with your code. Make sure to include any dependencies your code has within the compressed file. You can learn more about code packaging in the [Appwrite Cloud Functions tutorial](https:\/\/appwrite.io\/docs\/functions).\n\nUse the \"command\" param to set the entrypoint used to execute your code.",
+ "description": "Create a new function code deployment. Use this endpoint to upload a new version of your code function. To execute your newly uploaded code, you'll need to update the function's deployment to use your new deployment UID.\r\n\r\nThis endpoint accepts a tar.gz file compressed with your code. Make sure to include any dependencies your code has within the compressed file. You can learn more about code packaging in the [Appwrite Cloud Functions tutorial](https:\/\/appwrite.io\/docs\/functions).\r\n\r\nUse the \"command\" param to set the entrypoint used to execute your code.",
"responses": {
"202": {
"description": "Deployment",
@@ -10068,7 +10068,7 @@
"tags": [
"functions"
],
- "description": "Delete a function execution by its unique ID.\n",
+ "description": "Delete a function execution by its unique ID.\r\n",
"responses": {
"204": {
"description": "No content"
@@ -11279,7 +11279,7 @@
"tags": [
"health"
],
- "description": "Returns the amount of failed jobs in a given queue.\n",
+ "description": "Returns the amount of failed jobs in a given queue.\r\n",
"responses": {
"200": {
"description": "Health Queue",
@@ -12046,7 +12046,7 @@
"tags": [
"locale"
],
- "description": "Get the current user location based on IP. Returns an object with user country code, country name, continent name, continent code, ip address and suggested currency. You can use the locale header to get the data in a supported language.\n\n([IP Geolocation by DB-IP](https:\/\/db-ip.com))",
+ "description": "Get the current user location based on IP. Returns an object with user country code, country name, continent name, continent code, ip address and suggested currency. You can use the locale header to get the data in a supported language.\r\n\r\n([IP Geolocation by DB-IP](https:\/\/db-ip.com))",
"responses": {
"200": {
"description": "Locale",
@@ -12097,7 +12097,7 @@
},
"\/locale\/codes": {
"get": {
- "summary": "List Locale Codes",
+ "summary": "List locale codes",
"operationId": "localeListCodes",
"tags": [
"locale"
@@ -12720,7 +12720,7 @@
"tags": [
"messaging"
],
- "description": "Update an email message by its unique ID.\n",
+ "description": "Update an email message by its unique ID.\r\n",
"responses": {
"200": {
"description": "Message",
@@ -13027,7 +13027,7 @@
"tags": [
"messaging"
],
- "description": "Update a push notification by its unique ID.\n",
+ "description": "Update a push notification by its unique ID.\r\n",
"responses": {
"200": {
"description": "Message",
@@ -13299,7 +13299,7 @@
"tags": [
"messaging"
],
- "description": "Update an email message by its unique ID.\n",
+ "description": "Update an email message by its unique ID.\r\n",
"responses": {
"200": {
"description": "Message",
@@ -13414,7 +13414,7 @@
"tags": [
"messaging"
],
- "description": "Get a message by its unique ID.\n",
+ "description": "Get a message by its unique ID.\r\n",
"responses": {
"200": {
"description": "Message",
@@ -15915,7 +15915,7 @@
"tags": [
"messaging"
],
- "description": "Get a provider by its unique ID.\n",
+ "description": "Get a provider by its unique ID.\r\n",
"responses": {
"200": {
"description": "Provider",
@@ -16355,7 +16355,7 @@
"tags": [
"messaging"
],
- "description": "Get a topic by its unique ID.\n",
+ "description": "Get a topic by its unique ID.\r\n",
"responses": {
"200": {
"description": "Topic",
@@ -16418,7 +16418,7 @@
"tags": [
"messaging"
],
- "description": "Update a topic by its unique ID.\n",
+ "description": "Update a topic by its unique ID.\r\n",
"responses": {
"200": {
"description": "Topic",
@@ -16822,7 +16822,7 @@
"tags": [
"messaging"
],
- "description": "Get a subscriber by its unique ID.\n",
+ "description": "Get a subscriber by its unique ID.\r\n",
"responses": {
"200": {
"description": "Subscriber",
@@ -17516,7 +17516,7 @@
"tags": [
"storage"
],
- "description": "Create a new file. Before using this route, you should create a new bucket resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/storage#storageCreateBucket) API or directly from your Appwrite console.\n\nLarger files should be uploaded using multiple requests with the [content-range](https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Content-Range) header to send a partial request with a maximum supported chunk of `5MB`. The `content-range` header values should always be in bytes.\n\nWhen the first request is sent, the server will return the **File** object, and the subsequent part request must include the file's **id** in `x-appwrite-id` header to allow the server to know that the partial upload is for the existing file and not for a new one.\n\nIf you're creating a new file using one of the Appwrite SDKs, all the chunking logic will be managed by the SDK internally.\n",
+ "description": "Create a new file. Before using this route, you should create a new bucket resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/storage#storageCreateBucket) API or directly from your Appwrite console.\r\n\r\nLarger files should be uploaded using multiple requests with the [content-range](https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Content-Range) header to send a partial request with a maximum supported chunk of `5MB`. The `content-range` header values should always be in bytes.\r\n\r\nWhen the first request is sent, the server will return the **File** object, and the subsequent part request must include the file's **id** in `x-appwrite-id` header to allow the server to know that the partial upload is for the existing file and not for a new one.\r\n\r\nIf you're creating a new file using one of the Appwrite SDKs, all the chunking logic will be managed by the SDK internally.\r\n",
"responses": {
"201": {
"description": "File",
@@ -17789,7 +17789,7 @@
}
},
"delete": {
- "summary": "Delete File",
+ "summary": "Delete file",
"operationId": "storageDeleteFile",
"tags": [
"storage"
@@ -18137,7 +18137,8 @@
"jpeg",
"gif",
"png",
- "webp"
+ "webp",
+ "avif"
],
"x-enum-name": "ImageFormat",
"x-enum-keys": [],
@@ -18697,7 +18698,7 @@
"tags": [
"teams"
],
- "description": "Invite a new member to join your team. Provide an ID for existing users, or invite unregistered users using an email or phone number. If initiated from a Client SDK, Appwrite will send an email or sms with a link to join the team to the invited user, and an account will be created for them if one doesn't exist. If initiated from a Server SDK, the new member will be added automatically to the team.\n\nYou only need to provide one of a user ID, email, or phone number. Appwrite will prioritize accepting the user ID > email > phone number if you provide more than one of these parameters.\n\nUse the `url` parameter to redirect the user from the invitation email to your app. After the user is redirected, use the [Update Team Membership Status](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/teams#updateMembershipStatus) endpoint to allow the user to accept the invitation to the team. \n\nPlease note that to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) Appwrite will accept the only redirect URLs under the domains you have added as a platform on the Appwrite Console.\n",
+ "description": "Invite a new member to join your team. Provide an ID for existing users, or invite unregistered users using an email or phone number. If initiated from a Client SDK, Appwrite will send an email or sms with a link to join the team to the invited user, and an account will be created for them if one doesn't exist. If initiated from a Server SDK, the new member will be added automatically to the team.\r\n\r\nYou only need to provide one of a user ID, email, or phone number. Appwrite will prioritize accepting the user ID > email > phone number if you provide more than one of these parameters.\r\n\r\nUse the `url` parameter to redirect the user from the invitation email to your app. After the user is redirected, use the [Update Team Membership Status](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/teams#updateMembershipStatus) endpoint to allow the user to accept the invitation to the team. \r\n\r\nPlease note that to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) Appwrite will accept the only redirect URLs under the domains you have added as a platform on the Appwrite Console.\r\n",
"responses": {
"201": {
"description": "Membership",
@@ -18888,7 +18889,7 @@
"tags": [
"teams"
],
- "description": "Modify the roles of a team member. Only team members with the owner role have access to this endpoint. Learn more about [roles and permissions](https:\/\/appwrite.io\/docs\/permissions).\n",
+ "description": "Modify the roles of a team member. Only team members with the owner role have access to this endpoint. Learn more about [roles and permissions](https:\/\/appwrite.io\/docs\/permissions).\r\n",
"responses": {
"200": {
"description": "Membership",
@@ -19057,7 +19058,7 @@
"tags": [
"teams"
],
- "description": "Use this endpoint to allow a user to accept an invitation to join a team after being redirected back to your app from the invitation email received by the user.\n\nIf the request is successful, a session for the user is automatically created.\n",
+ "description": "Use this endpoint to allow a user to accept an invitation to join a team after being redirected back to your app from the invitation email received by the user.\r\n\r\nIf the request is successful, a session for the user is automatically created.\r\n",
"responses": {
"200": {
"description": "Membership",
@@ -19645,7 +19646,7 @@
},
"\/users\/identities": {
"get": {
- "summary": "List Identities",
+ "summary": "List identities",
"operationId": "usersListIdentities",
"tags": [
"users"
@@ -20580,7 +20581,7 @@
"tags": [
"users"
],
- "description": "Update the user labels by its unique ID. \n\nLabels can be used to grant access to resources. While teams are a way for user's to share access to a resource, labels can be defined by the developer to grant access without an invitation. See the [Permissions docs](https:\/\/appwrite.io\/docs\/permissions) for more info.",
+ "description": "Update the user labels by its unique ID. \r\n\r\nLabels can be used to grant access to resources. While teams are a way for user's to share access to a resource, labels can be defined by the developer to grant access without an invitation. See the [Permissions docs](https:\/\/appwrite.io\/docs\/permissions) for more info.",
"responses": {
"200": {
"description": "User",
@@ -20885,7 +20886,7 @@
},
"\/users\/{userId}\/mfa\/authenticators\/{type}": {
"delete": {
- "summary": "Delete Authenticator",
+ "summary": "Delete authenticator",
"operationId": "usersDeleteMfaAuthenticator",
"tags": [
"users"
@@ -20964,7 +20965,7 @@
},
"\/users\/{userId}\/mfa\/factors": {
"get": {
- "summary": "List Factors",
+ "summary": "List factors",
"operationId": "usersListMfaFactors",
"tags": [
"users"
@@ -21028,7 +21029,7 @@
},
"\/users\/{userId}\/mfa\/recovery-codes": {
"get": {
- "summary": "Get MFA Recovery Codes",
+ "summary": "Get MFA recovery codes",
"operationId": "usersGetMfaRecoveryCodes",
"tags": [
"users"
@@ -21090,7 +21091,7 @@
]
},
"put": {
- "summary": "Regenerate MFA Recovery Codes",
+ "summary": "Regenerate MFA recovery codes",
"operationId": "usersUpdateMfaRecoveryCodes",
"tags": [
"users"
@@ -21152,7 +21153,7 @@
]
},
"patch": {
- "summary": "Create MFA Recovery Codes",
+ "summary": "Create MFA recovery codes",
"operationId": "usersCreateMfaRecoveryCodes",
"tags": [
"users"
@@ -21677,7 +21678,7 @@
"tags": [
"users"
],
- "description": "Creates a session for a user. Returns an immediately usable session object.\n\nIf you want to generate a token for a custom authentication flow, use the [POST \/users\/{userId}\/tokens](https:\/\/appwrite.io\/docs\/server\/users#createToken) endpoint.",
+ "description": "Creates a session for a user. Returns an immediately usable session object.\r\n\r\nIf you want to generate a token for a custom authentication flow, use the [POST \/users\/{userId}\/tokens](https:\/\/appwrite.io\/docs\/server\/users#createToken) endpoint.",
"responses": {
"201": {
"description": "Session",
@@ -21941,7 +21942,7 @@
},
"\/users\/{userId}\/targets": {
"get": {
- "summary": "List User Targets",
+ "summary": "List user targets",
"operationId": "usersListTargets",
"tags": [
"users"
@@ -22017,7 +22018,7 @@
]
},
"post": {
- "summary": "Create User Target",
+ "summary": "Create user target",
"operationId": "usersCreateTarget",
"tags": [
"users"
@@ -22130,7 +22131,7 @@
},
"\/users\/{userId}\/targets\/{targetId}": {
"get": {
- "summary": "Get User Target",
+ "summary": "Get user target",
"operationId": "usersGetTarget",
"tags": [
"users"
@@ -22203,7 +22204,7 @@
]
},
"patch": {
- "summary": "Update User target",
+ "summary": "Update user target",
"operationId": "usersUpdateTarget",
"tags": [
"users"
@@ -22375,7 +22376,7 @@
"tags": [
"users"
],
- "description": "Returns a token with a secret key for creating a session. Use the user ID and secret and submit a request to the [PUT \/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process.\n",
+ "description": "Returns a token with a secret key for creating a session. Use the user ID and secret and submit a request to the [PUT \/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process.\r\n",
"responses": {
"201": {
"description": "Token",
@@ -23677,6 +23678,16 @@
"x-example": false,
"nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"size": {
"type": "integer",
"description": "Attribute size.",
@@ -23696,6 +23707,8 @@
"status",
"error",
"required",
+ "$createdAt",
+ "$updatedAt",
"size"
]
},
@@ -23734,6 +23747,16 @@
"x-example": false,
"nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"min": {
"type": "integer",
"description": "Minimum value to enforce for new documents.",
@@ -23761,7 +23784,9 @@
"type",
"status",
"error",
- "required"
+ "required",
+ "$createdAt",
+ "$updatedAt"
]
},
"attributeFloat": {
@@ -23799,6 +23824,16 @@
"x-example": false,
"nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"min": {
"type": "number",
"description": "Minimum value to enforce for new documents.",
@@ -23826,7 +23861,9 @@
"type",
"status",
"error",
- "required"
+ "required",
+ "$createdAt",
+ "$updatedAt"
]
},
"attributeBoolean": {
@@ -23864,6 +23901,16 @@
"x-example": false,
"nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"default": {
"type": "boolean",
"description": "Default value for attribute when not provided. Cannot be set when attribute is required.",
@@ -23876,7 +23923,9 @@
"type",
"status",
"error",
- "required"
+ "required",
+ "$createdAt",
+ "$updatedAt"
]
},
"attributeEmail": {
@@ -23914,6 +23963,16 @@
"x-example": false,
"nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"format": {
"type": "string",
"description": "String format.",
@@ -23932,6 +23991,8 @@
"status",
"error",
"required",
+ "$createdAt",
+ "$updatedAt",
"format"
]
},
@@ -23970,6 +24031,16 @@
"x-example": false,
"nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"elements": {
"type": "array",
"description": "Array of elements in enumerated type.",
@@ -23996,6 +24067,8 @@
"status",
"error",
"required",
+ "$createdAt",
+ "$updatedAt",
"elements",
"format"
]
@@ -24035,6 +24108,16 @@
"x-example": false,
"nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"format": {
"type": "string",
"description": "String format.",
@@ -24053,6 +24136,8 @@
"status",
"error",
"required",
+ "$createdAt",
+ "$updatedAt",
"format"
]
},
@@ -24091,6 +24176,16 @@
"x-example": false,
"nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"format": {
"type": "string",
"description": "String format.",
@@ -24109,6 +24204,8 @@
"status",
"error",
"required",
+ "$createdAt",
+ "$updatedAt",
"format"
]
},
@@ -24147,6 +24244,16 @@
"x-example": false,
"nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"format": {
"type": "string",
"description": "ISO 8601 format.",
@@ -24165,6 +24272,8 @@
"status",
"error",
"required",
+ "$createdAt",
+ "$updatedAt",
"format"
]
},
@@ -24203,6 +24312,16 @@
"x-example": false,
"nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"relatedCollection": {
"type": "string",
"description": "The ID of the related collection.",
@@ -24240,6 +24359,8 @@
"status",
"error",
"required",
+ "$createdAt",
+ "$updatedAt",
"relatedCollection",
"relationType",
"twoWay",
@@ -24288,6 +24409,16 @@
},
"x-example": [],
"nullable": true
+ },
+ "$createdAt": {
+ "type": "string",
+ "description": "Index creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Index update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
}
},
"required": [
@@ -24295,7 +24426,9 @@
"type",
"status",
"error",
- "attributes"
+ "attributes",
+ "$createdAt",
+ "$updatedAt"
]
},
"document": {
@@ -25966,7 +26099,7 @@
"responseBody": {
"type": "string",
"description": "HTTP response body. This will return empty unless execution is created as synchronous.",
- "x-example": "Developers are awesome."
+ "x-example": ""
},
"responseHeaders": {
"type": "array",
diff --git a/app/config/specs/swagger2-1.6.x-client.json b/app/config/specs/swagger2-1.6.x-client.json
index ce9ea857bb..fce6a871a3 100644
--- a/app/config/specs/swagger2-1.6.x-client.json
+++ b/app/config/specs/swagger2-1.6.x-client.json
@@ -222,7 +222,7 @@
"tags": [
"account"
],
- "description": "Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. For security measures, user password is required to complete this request.\nThis endpoint can also be used to convert an anonymous account to a normal one, by passing an email address and a new password.\n",
+ "description": "Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. For security measures, user password is required to complete this request.\r\nThis endpoint can also be used to convert an anonymous account to a normal one, by passing an email address and a new password.\r\n",
"responses": {
"200": {
"description": "User",
@@ -293,7 +293,7 @@
},
"\/account\/identities": {
"get": {
- "summary": "List Identities",
+ "summary": "List identities",
"operationId": "accountListIdentities",
"consumes": [
"application\/json"
@@ -619,7 +619,7 @@
},
"\/account\/mfa\/authenticators\/{type}": {
"post": {
- "summary": "Create Authenticator",
+ "summary": "Create authenticator",
"operationId": "accountCreateMfaAuthenticator",
"consumes": [
"application\/json"
@@ -687,7 +687,7 @@
]
},
"put": {
- "summary": "Verify Authenticator",
+ "summary": "Verify authenticator",
"operationId": "accountUpdateMfaAuthenticator",
"consumes": [
"application\/json"
@@ -773,7 +773,7 @@
]
},
"delete": {
- "summary": "Delete Authenticator",
+ "summary": "Delete authenticator",
"operationId": "accountDeleteMfaAuthenticator",
"consumes": [
"application\/json"
@@ -838,7 +838,7 @@
},
"\/account\/mfa\/challenge": {
"post": {
- "summary": "Create MFA Challenge",
+ "summary": "Create MFA challenge",
"operationId": "accountCreateMfaChallenge",
"consumes": [
"application\/json"
@@ -917,7 +917,7 @@
]
},
"put": {
- "summary": "Create MFA Challenge (confirmation)",
+ "summary": "Create MFA challenge (confirmation)",
"operationId": "accountUpdateMfaChallenge",
"consumes": [
"application\/json"
@@ -994,7 +994,7 @@
},
"\/account\/mfa\/factors": {
"get": {
- "summary": "List Factors",
+ "summary": "List factors",
"operationId": "accountListMfaFactors",
"consumes": [
"application\/json"
@@ -1049,7 +1049,7 @@
},
"\/account\/mfa\/recovery-codes": {
"get": {
- "summary": "Get MFA Recovery Codes",
+ "summary": "Get MFA recovery codes",
"operationId": "accountGetMfaRecoveryCodes",
"consumes": [
"application\/json"
@@ -1102,7 +1102,7 @@
]
},
"post": {
- "summary": "Create MFA Recovery Codes",
+ "summary": "Create MFA recovery codes",
"operationId": "accountCreateMfaRecoveryCodes",
"consumes": [
"application\/json"
@@ -1155,7 +1155,7 @@
]
},
"patch": {
- "summary": "Regenerate MFA Recovery Codes",
+ "summary": "Regenerate MFA recovery codes",
"operationId": "accountUpdateMfaRecoveryCodes",
"consumes": [
"application\/json"
@@ -1670,7 +1670,7 @@
"tags": [
"account"
],
- "description": "Use this endpoint to complete the user account password reset. Both the **userId** and **secret** arguments will be passed as query parameters to the redirect URL you have provided when sending your request to the [POST \/account\/recovery](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createRecovery) endpoint.\n\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.",
+ "description": "Use this endpoint to complete the user account password reset. Both the **userId** and **secret** arguments will be passed as query parameters to the redirect URL you have provided when sending your request to the [POST \/account\/recovery](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createRecovery) endpoint.\r\n\r\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.",
"responses": {
"200": {
"description": "Token",
@@ -1915,7 +1915,7 @@
"tags": [
"account"
],
- "description": "Allow the user to login into their account by providing a valid email and password combination. This route will create a new session for the user.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
+ "description": "Allow the user to login into their account by providing a valid email and password combination. This route will create a new session for the user.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
"responses": {
"201": {
"description": "Session",
@@ -2075,7 +2075,7 @@
"tags": [
"account"
],
- "description": "Allow the user to login to their account using the OAuth2 provider of their choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed.\n\nIf there is already an active session, the new session will be attached to the logged-in account. If there are no active sessions, the server will attempt to look for a user with the same email address as the email received from the OAuth2 provider and attach the new session to the existing user. If no matching user is found - the server will create a new user.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\n",
+ "description": "Allow the user to login to their account using the OAuth2 provider of their choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed.\r\n\r\nIf there is already an active session, the new session will be attached to the logged-in account. If there are no active sessions, the server will attempt to look for a user with the same email address as the email received from the OAuth2 provider and attach the new session to the existing user. If no matching user is found - the server will create a new user.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\r\n",
"responses": {
"301": {
"description": "No content"
@@ -2836,7 +2836,7 @@
"tags": [
"account"
],
- "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's email is valid for 15 minutes.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
+ "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's email is valid for 15 minutes.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
"responses": {
"201": {
"description": "Token",
@@ -2922,7 +2922,7 @@
"tags": [
"account"
],
- "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour. If you are on a mobile device you can leave the URL parameter empty, so that the login completion will be handled by your Appwrite instance by default.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\n",
+ "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour. If you are on a mobile device you can leave the URL parameter empty, so that the login completion will be handled by your Appwrite instance by default.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\r\n",
"responses": {
"201": {
"description": "Token",
@@ -3017,7 +3017,7 @@
"tags": [
"account"
],
- "description": "Allow the user to login to their account using the OAuth2 provider of their choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed. \n\nIf authentication succeeds, `userId` and `secret` of a token will be appended to the success URL as query parameters. These can be used to create a new session using the [Create session](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
+ "description": "Allow the user to login to their account using the OAuth2 provider of their choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed. \r\n\r\nIf authentication succeeds, `userId` and `secret` of a token will be appended to the success URL as query parameters. These can be used to create a new session using the [Create session](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
"responses": {
"301": {
"description": "No content"
@@ -3152,7 +3152,7 @@
"tags": [
"account"
],
- "description": "Sends the user an SMS with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's phone is valid for 15 minutes.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
+ "description": "Sends the user an SMS with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's phone is valid for 15 minutes.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
"responses": {
"201": {
"description": "Token",
@@ -3235,7 +3235,7 @@
"tags": [
"account"
],
- "description": "Use this endpoint to send a verification message to your user email address to confirm they are the valid owners of that address. Both the **userId** and **secret** arguments will be passed as query parameters to the URL you have provided to be attached to the verification email. The provided URL should redirect the user back to your app and allow you to complete the verification process by verifying both the **userId** and **secret** parameters. Learn more about how to [complete the verification process](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\n\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.\n",
+ "description": "Use this endpoint to send a verification message to your user email address to confirm they are the valid owners of that address. Both the **userId** and **secret** arguments will be passed as query parameters to the URL you have provided to be attached to the verification email. The provided URL should redirect the user back to your app and allow you to complete the verification process by verifying both the **userId** and **secret** parameters. Learn more about how to [complete the verification process](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\r\n\r\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.\r\n",
"responses": {
"201": {
"description": "Token",
@@ -3528,7 +3528,7 @@
"tags": [
"avatars"
],
- "description": "You can use this endpoint to show different browser icons to your users. The code argument receives the browser code as it appears in your user [GET \/account\/sessions](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#getSessions) endpoint. Use width, height and quality arguments to change the output settings.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.",
+ "description": "You can use this endpoint to show different browser icons to your users. The code argument receives the browser code as it appears in your user [GET \/account\/sessions](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#getSessions) endpoint. Use width, height and quality arguments to change the output settings.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.",
"responses": {
"200": {
"description": "Image",
@@ -3657,7 +3657,7 @@
"tags": [
"avatars"
],
- "description": "The credit card endpoint will return you the icon of the credit card provider you need. Use width, height and quality arguments to change the output settings.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n",
+ "description": "The credit card endpoint will return you the icon of the credit card provider you need. Use width, height and quality arguments to change the output settings.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n",
"responses": {
"200": {
"description": "Image",
@@ -3790,7 +3790,7 @@
"tags": [
"avatars"
],
- "description": "Use this endpoint to fetch the favorite icon (AKA favicon) of any remote website URL.\n\nThis endpoint does not follow HTTP redirects.",
+ "description": "Use this endpoint to fetch the favorite icon (AKA favicon) of any remote website URL.\r\n\r\nThis endpoint does not follow HTTP redirects.",
"responses": {
"200": {
"description": "Image",
@@ -3857,7 +3857,7 @@
"tags": [
"avatars"
],
- "description": "You can use this endpoint to show different country flags icons to your users. The code argument receives the 2 letter country code. Use width, height and quality arguments to change the output settings. Country codes follow the [ISO 3166-1](https:\/\/en.wikipedia.org\/wiki\/ISO_3166-1) standard.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n",
+ "description": "You can use this endpoint to show different country flags icons to your users. The code argument receives the 2 letter country code. Use width, height and quality arguments to change the output settings. Country codes follow the [ISO 3166-1](https:\/\/en.wikipedia.org\/wiki\/ISO_3166-1) standard.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n",
"responses": {
"200": {
"description": "Image",
@@ -4348,7 +4348,7 @@
"tags": [
"avatars"
],
- "description": "Use this endpoint to fetch a remote image URL and crop it to any image size you want. This endpoint is very useful if you need to crop and display remote images in your app or in case you want to make sure a 3rd party image is properly served using a TLS protocol.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 400x400px.\n\nThis endpoint does not follow HTTP redirects.",
+ "description": "Use this endpoint to fetch a remote image URL and crop it to any image size you want. This endpoint is very useful if you need to crop and display remote images in your app or in case you want to make sure a 3rd party image is properly served using a TLS protocol.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 400x400px.\r\n\r\nThis endpoint does not follow HTTP redirects.",
"responses": {
"200": {
"description": "Image",
@@ -4435,7 +4435,7 @@
"tags": [
"avatars"
],
- "description": "Use this endpoint to show your user initials avatar icon on your website or app. By default, this route will try to print your logged-in user name or email initials. You can also overwrite the user name if you pass the 'name' parameter. If no name is given and no user is logged, an empty avatar will be returned.\n\nYou can use the color and background params to change the avatar colors. By default, a random theme will be selected. The random theme will persist for the user's initials when reloading the same theme will always return for the same initials.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n",
+ "description": "Use this endpoint to show your user initials avatar icon on your website or app. By default, this route will try to print your logged-in user name or email initials. You can also overwrite the user name if you pass the 'name' parameter. If no name is given and no user is logged, an empty avatar will be returned.\r\n\r\nYou can use the color and background params to change the avatar colors. By default, a random theme will be selected. The random theme will persist for the user's initials when reloading the same theme will always return for the same initials.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n",
"responses": {
"200": {
"description": "Image",
@@ -4530,7 +4530,7 @@
"tags": [
"avatars"
],
- "description": "Converts a given plain text to a QR code image. You can use the query parameters to change the size and style of the resulting image.\n",
+ "description": "Converts a given plain text to a QR code image. You can use the query parameters to change the size and style of the resulting image.\r\n",
"responses": {
"200": {
"description": "Image",
@@ -5528,7 +5528,7 @@
"tags": [
"locale"
],
- "description": "Get the current user location based on IP. Returns an object with user country code, country name, continent name, continent code, ip address and suggested currency. You can use the locale header to get the data in a supported language.\n\n([IP Geolocation by DB-IP](https:\/\/db-ip.com))",
+ "description": "Get the current user location based on IP. Returns an object with user country code, country name, continent name, continent code, ip address and suggested currency. You can use the locale header to get the data in a supported language.\r\n\r\n([IP Geolocation by DB-IP](https:\/\/db-ip.com))",
"responses": {
"200": {
"description": "Locale",
@@ -5573,7 +5573,7 @@
},
"\/locale\/codes": {
"get": {
- "summary": "List Locale Codes",
+ "summary": "List locale codes",
"operationId": "localeListCodes",
"consumes": [
"application\/json"
@@ -6225,7 +6225,7 @@
"tags": [
"storage"
],
- "description": "Create a new file. Before using this route, you should create a new bucket resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/storage#storageCreateBucket) API or directly from your Appwrite console.\n\nLarger files should be uploaded using multiple requests with the [content-range](https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Content-Range) header to send a partial request with a maximum supported chunk of `5MB`. The `content-range` header values should always be in bytes.\n\nWhen the first request is sent, the server will return the **File** object, and the subsequent part request must include the file's **id** in `x-appwrite-id` header to allow the server to know that the partial upload is for the existing file and not for a new one.\n\nIf you're creating a new file using one of the Appwrite SDKs, all the chunking logic will be managed by the SDK internally.\n",
+ "description": "Create a new file. Before using this route, you should create a new bucket resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/storage#storageCreateBucket) API or directly from your Appwrite console.\r\n\r\nLarger files should be uploaded using multiple requests with the [content-range](https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Content-Range) header to send a partial request with a maximum supported chunk of `5MB`. The `content-range` header values should always be in bytes.\r\n\r\nWhen the first request is sent, the server will return the **File** object, and the subsequent part request must include the file's **id** in `x-appwrite-id` header to allow the server to know that the partial upload is for the existing file and not for a new one.\r\n\r\nIf you're creating a new file using one of the Appwrite SDKs, all the chunking logic will be managed by the SDK internally.\r\n",
"responses": {
"201": {
"description": "File",
@@ -6476,7 +6476,7 @@
]
},
"delete": {
- "summary": "Delete File",
+ "summary": "Delete file",
"operationId": "storageDeleteFile",
"consumes": [
"application\/json"
@@ -6807,7 +6807,8 @@
"jpeg",
"gif",
"png",
- "webp"
+ "webp",
+ "avif"
],
"x-enum-name": "ImageFormat",
"x-enum-keys": [],
@@ -7367,7 +7368,7 @@
"tags": [
"teams"
],
- "description": "Invite a new member to join your team. Provide an ID for existing users, or invite unregistered users using an email or phone number. If initiated from a Client SDK, Appwrite will send an email or sms with a link to join the team to the invited user, and an account will be created for them if one doesn't exist. If initiated from a Server SDK, the new member will be added automatically to the team.\n\nYou only need to provide one of a user ID, email, or phone number. Appwrite will prioritize accepting the user ID > email > phone number if you provide more than one of these parameters.\n\nUse the `url` parameter to redirect the user from the invitation email to your app. After the user is redirected, use the [Update Team Membership Status](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/teams#updateMembershipStatus) endpoint to allow the user to accept the invitation to the team. \n\nPlease note that to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) Appwrite will accept the only redirect URLs under the domains you have added as a platform on the Appwrite Console.\n",
+ "description": "Invite a new member to join your team. Provide an ID for existing users, or invite unregistered users using an email or phone number. If initiated from a Client SDK, Appwrite will send an email or sms with a link to join the team to the invited user, and an account will be created for them if one doesn't exist. If initiated from a Server SDK, the new member will be added automatically to the team.\r\n\r\nYou only need to provide one of a user ID, email, or phone number. Appwrite will prioritize accepting the user ID > email > phone number if you provide more than one of these parameters.\r\n\r\nUse the `url` parameter to redirect the user from the invitation email to your app. After the user is redirected, use the [Update Team Membership Status](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/teams#updateMembershipStatus) endpoint to allow the user to accept the invitation to the team. \r\n\r\nPlease note that to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) Appwrite will accept the only redirect URLs under the domains you have added as a platform on the Appwrite Console.\r\n",
"responses": {
"201": {
"description": "Membership",
@@ -7556,7 +7557,7 @@
"tags": [
"teams"
],
- "description": "Modify the roles of a team member. Only team members with the owner role have access to this endpoint. Learn more about [roles and permissions](https:\/\/appwrite.io\/docs\/permissions).\n",
+ "description": "Modify the roles of a team member. Only team members with the owner role have access to this endpoint. Learn more about [roles and permissions](https:\/\/appwrite.io\/docs\/permissions).\r\n",
"responses": {
"200": {
"description": "Membership",
@@ -7718,7 +7719,7 @@
"tags": [
"teams"
],
- "description": "Use this endpoint to allow a user to accept an invitation to join a team after being redirected back to your app from the invitation email received by the user.\n\nIf the request is successful, a session for the user is automatically created.\n",
+ "description": "Use this endpoint to allow a user to accept an invitation to join a team after being redirected back to your app from the invitation email received by the user.\r\n\r\nIf the request is successful, a session for the user is automatically created.\r\n",
"responses": {
"200": {
"description": "Membership",
@@ -9589,9 +9590,9 @@
"format": "int32"
},
"responseBody": {
- "type": "string",
+ "type": "payload",
"description": "HTTP response body. This will return empty unless execution is created as synchronous.",
- "x-example": "Developers are awesome."
+ "x-example": ""
},
"responseHeaders": {
"type": "array",
diff --git a/app/config/specs/swagger2-1.6.x-console.json b/app/config/specs/swagger2-1.6.x-console.json
index 51935a5e01..9200c80593 100644
--- a/app/config/specs/swagger2-1.6.x-console.json
+++ b/app/config/specs/swagger2-1.6.x-console.json
@@ -278,7 +278,7 @@
"tags": [
"account"
],
- "description": "Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. For security measures, user password is required to complete this request.\nThis endpoint can also be used to convert an anonymous account to a normal one, by passing an email address and a new password.\n",
+ "description": "Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. For security measures, user password is required to complete this request.\r\nThis endpoint can also be used to convert an anonymous account to a normal one, by passing an email address and a new password.\r\n",
"responses": {
"200": {
"description": "User",
@@ -348,7 +348,7 @@
},
"\/account\/identities": {
"get": {
- "summary": "List Identities",
+ "summary": "List identities",
"operationId": "accountListIdentities",
"consumes": [
"application\/json"
@@ -670,7 +670,7 @@
},
"\/account\/mfa\/authenticators\/{type}": {
"post": {
- "summary": "Create Authenticator",
+ "summary": "Create authenticator",
"operationId": "accountCreateMfaAuthenticator",
"consumes": [
"application\/json"
@@ -737,7 +737,7 @@
]
},
"put": {
- "summary": "Verify Authenticator",
+ "summary": "Verify authenticator",
"operationId": "accountUpdateMfaAuthenticator",
"consumes": [
"application\/json"
@@ -822,7 +822,7 @@
]
},
"delete": {
- "summary": "Delete Authenticator",
+ "summary": "Delete authenticator",
"operationId": "accountDeleteMfaAuthenticator",
"consumes": [
"application\/json"
@@ -886,7 +886,7 @@
},
"\/account\/mfa\/challenge": {
"post": {
- "summary": "Create MFA Challenge",
+ "summary": "Create MFA challenge",
"operationId": "accountCreateMfaChallenge",
"consumes": [
"application\/json"
@@ -965,7 +965,7 @@
]
},
"put": {
- "summary": "Create MFA Challenge (confirmation)",
+ "summary": "Create MFA challenge (confirmation)",
"operationId": "accountUpdateMfaChallenge",
"consumes": [
"application\/json"
@@ -1041,7 +1041,7 @@
},
"\/account\/mfa\/factors": {
"get": {
- "summary": "List Factors",
+ "summary": "List factors",
"operationId": "accountListMfaFactors",
"consumes": [
"application\/json"
@@ -1095,7 +1095,7 @@
},
"\/account\/mfa\/recovery-codes": {
"get": {
- "summary": "Get MFA Recovery Codes",
+ "summary": "Get MFA recovery codes",
"operationId": "accountGetMfaRecoveryCodes",
"consumes": [
"application\/json"
@@ -1147,7 +1147,7 @@
]
},
"post": {
- "summary": "Create MFA Recovery Codes",
+ "summary": "Create MFA recovery codes",
"operationId": "accountCreateMfaRecoveryCodes",
"consumes": [
"application\/json"
@@ -1199,7 +1199,7 @@
]
},
"patch": {
- "summary": "Regenerate MFA Recovery Codes",
+ "summary": "Regenerate MFA recovery codes",
"operationId": "accountUpdateMfaRecoveryCodes",
"consumes": [
"application\/json"
@@ -1707,7 +1707,7 @@
"tags": [
"account"
],
- "description": "Use this endpoint to complete the user account password reset. Both the **userId** and **secret** arguments will be passed as query parameters to the redirect URL you have provided when sending your request to the [POST \/account\/recovery](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createRecovery) endpoint.\n\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.",
+ "description": "Use this endpoint to complete the user account password reset. Both the **userId** and **secret** arguments will be passed as query parameters to the redirect URL you have provided when sending your request to the [POST \/account\/recovery](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createRecovery) endpoint.\r\n\r\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.",
"responses": {
"200": {
"description": "Token",
@@ -1949,7 +1949,7 @@
"tags": [
"account"
],
- "description": "Allow the user to login into their account by providing a valid email and password combination. This route will create a new session for the user.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
+ "description": "Allow the user to login into their account by providing a valid email and password combination. This route will create a new session for the user.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
"responses": {
"201": {
"description": "Session",
@@ -2109,7 +2109,7 @@
"tags": [
"account"
],
- "description": "Allow the user to login to their account using the OAuth2 provider of their choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed.\n\nIf there is already an active session, the new session will be attached to the logged-in account. If there are no active sessions, the server will attempt to look for a user with the same email address as the email received from the OAuth2 provider and attach the new session to the existing user. If no matching user is found - the server will create a new user.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\n",
+ "description": "Allow the user to login to their account using the OAuth2 provider of their choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed.\r\n\r\nIf there is already an active session, the new session will be attached to the logged-in account. If there are no active sessions, the server will attempt to look for a user with the same email address as the email received from the OAuth2 provider and attach the new session to the existing user. If no matching user is found - the server will create a new user.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\r\n",
"responses": {
"301": {
"description": "No content"
@@ -2863,7 +2863,7 @@
"tags": [
"account"
],
- "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's email is valid for 15 minutes.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
+ "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's email is valid for 15 minutes.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
"responses": {
"201": {
"description": "Token",
@@ -2949,7 +2949,7 @@
"tags": [
"account"
],
- "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour. If you are on a mobile device you can leave the URL parameter empty, so that the login completion will be handled by your Appwrite instance by default.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\n",
+ "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour. If you are on a mobile device you can leave the URL parameter empty, so that the login completion will be handled by your Appwrite instance by default.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\r\n",
"responses": {
"201": {
"description": "Token",
@@ -3044,7 +3044,7 @@
"tags": [
"account"
],
- "description": "Allow the user to login to their account using the OAuth2 provider of their choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed. \n\nIf authentication succeeds, `userId` and `secret` of a token will be appended to the success URL as query parameters. These can be used to create a new session using the [Create session](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
+ "description": "Allow the user to login to their account using the OAuth2 provider of their choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed. \r\n\r\nIf authentication succeeds, `userId` and `secret` of a token will be appended to the success URL as query parameters. These can be used to create a new session using the [Create session](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
"responses": {
"301": {
"description": "No content"
@@ -3179,7 +3179,7 @@
"tags": [
"account"
],
- "description": "Sends the user an SMS with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's phone is valid for 15 minutes.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
+ "description": "Sends the user an SMS with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's phone is valid for 15 minutes.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
"responses": {
"201": {
"description": "Token",
@@ -3262,7 +3262,7 @@
"tags": [
"account"
],
- "description": "Use this endpoint to send a verification message to your user email address to confirm they are the valid owners of that address. Both the **userId** and **secret** arguments will be passed as query parameters to the URL you have provided to be attached to the verification email. The provided URL should redirect the user back to your app and allow you to complete the verification process by verifying both the **userId** and **secret** parameters. Learn more about how to [complete the verification process](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\n\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.\n",
+ "description": "Use this endpoint to send a verification message to your user email address to confirm they are the valid owners of that address. Both the **userId** and **secret** arguments will be passed as query parameters to the URL you have provided to be attached to the verification email. The provided URL should redirect the user back to your app and allow you to complete the verification process by verifying both the **userId** and **secret** parameters. Learn more about how to [complete the verification process](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\r\n\r\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.\r\n",
"responses": {
"201": {
"description": "Token",
@@ -3551,7 +3551,7 @@
"tags": [
"avatars"
],
- "description": "You can use this endpoint to show different browser icons to your users. The code argument receives the browser code as it appears in your user [GET \/account\/sessions](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#getSessions) endpoint. Use width, height and quality arguments to change the output settings.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.",
+ "description": "You can use this endpoint to show different browser icons to your users. The code argument receives the browser code as it appears in your user [GET \/account\/sessions](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#getSessions) endpoint. Use width, height and quality arguments to change the output settings.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.",
"responses": {
"200": {
"description": "Image",
@@ -3680,7 +3680,7 @@
"tags": [
"avatars"
],
- "description": "The credit card endpoint will return you the icon of the credit card provider you need. Use width, height and quality arguments to change the output settings.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n",
+ "description": "The credit card endpoint will return you the icon of the credit card provider you need. Use width, height and quality arguments to change the output settings.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n",
"responses": {
"200": {
"description": "Image",
@@ -3813,7 +3813,7 @@
"tags": [
"avatars"
],
- "description": "Use this endpoint to fetch the favorite icon (AKA favicon) of any remote website URL.\n\nThis endpoint does not follow HTTP redirects.",
+ "description": "Use this endpoint to fetch the favorite icon (AKA favicon) of any remote website URL.\r\n\r\nThis endpoint does not follow HTTP redirects.",
"responses": {
"200": {
"description": "Image",
@@ -3880,7 +3880,7 @@
"tags": [
"avatars"
],
- "description": "You can use this endpoint to show different country flags icons to your users. The code argument receives the 2 letter country code. Use width, height and quality arguments to change the output settings. Country codes follow the [ISO 3166-1](https:\/\/en.wikipedia.org\/wiki\/ISO_3166-1) standard.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n",
+ "description": "You can use this endpoint to show different country flags icons to your users. The code argument receives the 2 letter country code. Use width, height and quality arguments to change the output settings. Country codes follow the [ISO 3166-1](https:\/\/en.wikipedia.org\/wiki\/ISO_3166-1) standard.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n",
"responses": {
"200": {
"description": "Image",
@@ -4371,7 +4371,7 @@
"tags": [
"avatars"
],
- "description": "Use this endpoint to fetch a remote image URL and crop it to any image size you want. This endpoint is very useful if you need to crop and display remote images in your app or in case you want to make sure a 3rd party image is properly served using a TLS protocol.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 400x400px.\n\nThis endpoint does not follow HTTP redirects.",
+ "description": "Use this endpoint to fetch a remote image URL and crop it to any image size you want. This endpoint is very useful if you need to crop and display remote images in your app or in case you want to make sure a 3rd party image is properly served using a TLS protocol.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 400x400px.\r\n\r\nThis endpoint does not follow HTTP redirects.",
"responses": {
"200": {
"description": "Image",
@@ -4458,7 +4458,7 @@
"tags": [
"avatars"
],
- "description": "Use this endpoint to show your user initials avatar icon on your website or app. By default, this route will try to print your logged-in user name or email initials. You can also overwrite the user name if you pass the 'name' parameter. If no name is given and no user is logged, an empty avatar will be returned.\n\nYou can use the color and background params to change the avatar colors. By default, a random theme will be selected. The random theme will persist for the user's initials when reloading the same theme will always return for the same initials.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n",
+ "description": "Use this endpoint to show your user initials avatar icon on your website or app. By default, this route will try to print your logged-in user name or email initials. You can also overwrite the user name if you pass the 'name' parameter. If no name is given and no user is logged, an empty avatar will be returned.\r\n\r\nYou can use the color and background params to change the avatar colors. By default, a random theme will be selected. The random theme will persist for the user's initials when reloading the same theme will always return for the same initials.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n",
"responses": {
"200": {
"description": "Image",
@@ -4553,7 +4553,7 @@
"tags": [
"avatars"
],
- "description": "Converts a given plain text to a QR code image. You can use the query parameters to change the size and style of the resulting image.\n",
+ "description": "Converts a given plain text to a QR code image. You can use the query parameters to change the size and style of the resulting image.\r\n",
"responses": {
"200": {
"description": "Image",
@@ -4637,7 +4637,7 @@
},
"\/console\/assistant": {
"post": {
- "summary": "Ask Query",
+ "summary": "Ask query",
"operationId": "assistantChat",
"consumes": [
"application\/json"
@@ -4846,7 +4846,7 @@
"tags": [
"databases"
],
- "description": "Create a new Database.\n",
+ "description": "Create a new Database.\r\n",
"responses": {
"201": {
"description": "Database",
@@ -5727,7 +5727,7 @@
"tags": [
"databases"
],
- "description": "Create a boolean attribute.\n",
+ "description": "Create a boolean attribute.\r\n",
"responses": {
"202": {
"description": "AttributeBoolean",
@@ -6159,7 +6159,7 @@
"tags": [
"databases"
],
- "description": "Create an email attribute.\n",
+ "description": "Create an email attribute.\r\n",
"responses": {
"202": {
"description": "AttributeEmail",
@@ -6265,7 +6265,7 @@
"tags": [
"databases"
],
- "description": "Update an email attribute. Changing the `default` value will not update already existing documents.\n",
+ "description": "Update an email attribute. Changing the `default` value will not update already existing documents.\r\n",
"responses": {
"200": {
"description": "AttributeEmail",
@@ -6375,7 +6375,7 @@
"tags": [
"databases"
],
- "description": "Create an enumeration attribute. The `elements` param acts as a white-list of accepted values for this attribute. \n",
+ "description": "Create an enumeration attribute. The `elements` param acts as a white-list of accepted values for this attribute. \r\n",
"responses": {
"202": {
"description": "AttributeEnum",
@@ -6491,7 +6491,7 @@
"tags": [
"databases"
],
- "description": "Update an enum attribute. Changing the `default` value will not update already existing documents.\n",
+ "description": "Update an enum attribute. Changing the `default` value will not update already existing documents.\r\n",
"responses": {
"200": {
"description": "AttributeEnum",
@@ -6611,7 +6611,7 @@
"tags": [
"databases"
],
- "description": "Create a float attribute. Optionally, minimum and maximum values can be provided.\n",
+ "description": "Create a float attribute. Optionally, minimum and maximum values can be provided.\r\n",
"responses": {
"202": {
"description": "AttributeFloat",
@@ -6729,7 +6729,7 @@
"tags": [
"databases"
],
- "description": "Update a float attribute. Changing the `default` value will not update already existing documents.\n",
+ "description": "Update a float attribute. Changing the `default` value will not update already existing documents.\r\n",
"responses": {
"200": {
"description": "AttributeFloat",
@@ -6853,7 +6853,7 @@
"tags": [
"databases"
],
- "description": "Create an integer attribute. Optionally, minimum and maximum values can be provided.\n",
+ "description": "Create an integer attribute. Optionally, minimum and maximum values can be provided.\r\n",
"responses": {
"202": {
"description": "AttributeInteger",
@@ -6971,7 +6971,7 @@
"tags": [
"databases"
],
- "description": "Update an integer attribute. Changing the `default` value will not update already existing documents.\n",
+ "description": "Update an integer attribute. Changing the `default` value will not update already existing documents.\r\n",
"responses": {
"200": {
"description": "AttributeInteger",
@@ -7095,7 +7095,7 @@
"tags": [
"databases"
],
- "description": "Create IP address attribute.\n",
+ "description": "Create IP address attribute.\r\n",
"responses": {
"202": {
"description": "AttributeIP",
@@ -7201,7 +7201,7 @@
"tags": [
"databases"
],
- "description": "Update an ip attribute. Changing the `default` value will not update already existing documents.\n",
+ "description": "Update an ip attribute. Changing the `default` value will not update already existing documents.\r\n",
"responses": {
"200": {
"description": "AttributeIP",
@@ -7311,7 +7311,7 @@
"tags": [
"databases"
],
- "description": "Create relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\n",
+ "description": "Create relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\r\n",
"responses": {
"202": {
"description": "AttributeRelationship",
@@ -7446,7 +7446,7 @@
"tags": [
"databases"
],
- "description": "Create a string attribute.\n",
+ "description": "Create a string attribute.\r\n",
"responses": {
"202": {
"description": "AttributeString",
@@ -7565,7 +7565,7 @@
"tags": [
"databases"
],
- "description": "Update a string attribute. Changing the `default` value will not update already existing documents.\n",
+ "description": "Update a string attribute. Changing the `default` value will not update already existing documents.\r\n",
"responses": {
"200": {
"description": "AttributeString",
@@ -7681,7 +7681,7 @@
"tags": [
"databases"
],
- "description": "Create a URL attribute.\n",
+ "description": "Create a URL attribute.\r\n",
"responses": {
"202": {
"description": "AttributeURL",
@@ -7787,7 +7787,7 @@
"tags": [
"databases"
],
- "description": "Update an url attribute. Changing the `default` value will not update already existing documents.\n",
+ "description": "Update an url attribute. Changing the `default` value will not update already existing documents.\r\n",
"responses": {
"200": {
"description": "AttributeURL",
@@ -8075,7 +8075,7 @@
"tags": [
"databases"
],
- "description": "Update relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\n",
+ "description": "Update relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\r\n",
"responses": {
"200": {
"description": "AttributeRelationship",
@@ -8817,7 +8817,7 @@
"tags": [
"databases"
],
- "description": "Creates an index on the attributes listed. Your index should include all the attributes you will query in a single request.\nAttributes can be `key`, `fulltext`, and `unique`.",
+ "description": "Creates an index on the attributes listed. Your index should include all the attributes you will query in a single request.\r\nAttributes can be `key`, `fulltext`, and `unique`.",
"responses": {
"202": {
"description": "Index",
@@ -9806,7 +9806,7 @@
"tags": [
"functions"
],
- "description": "List allowed function specifications for this instance.\n",
+ "description": "List allowed function specifications for this instance.\r\n",
"responses": {
"200": {
"description": "Specifications List",
@@ -10533,7 +10533,7 @@
"tags": [
"functions"
],
- "description": "Create a new function code deployment. Use this endpoint to upload a new version of your code function. To execute your newly uploaded code, you'll need to update the function's deployment to use your new deployment UID.\n\nThis endpoint accepts a tar.gz file compressed with your code. Make sure to include any dependencies your code has within the compressed file. You can learn more about code packaging in the [Appwrite Cloud Functions tutorial](https:\/\/appwrite.io\/docs\/functions).\n\nUse the \"command\" param to set the entrypoint used to execute your code.",
+ "description": "Create a new function code deployment. Use this endpoint to upload a new version of your code function. To execute your newly uploaded code, you'll need to update the function's deployment to use your new deployment UID.\r\n\r\nThis endpoint accepts a tar.gz file compressed with your code. Make sure to include any dependencies your code has within the compressed file. You can learn more about code packaging in the [Appwrite Cloud Functions tutorial](https:\/\/appwrite.io\/docs\/functions).\r\n\r\nUse the \"command\" param to set the entrypoint used to execute your code.",
"responses": {
"202": {
"description": "Deployment",
@@ -11336,7 +11336,7 @@
"tags": [
"functions"
],
- "description": "Delete a function execution by its unique ID.\n",
+ "description": "Delete a function execution by its unique ID.\r\n",
"responses": {
"204": {
"description": "No content"
@@ -12660,7 +12660,7 @@
"tags": [
"health"
],
- "description": "Returns the amount of failed jobs in a given queue.\n",
+ "description": "Returns the amount of failed jobs in a given queue.\r\n",
"responses": {
"200": {
"description": "Health Queue",
@@ -13419,7 +13419,7 @@
"tags": [
"locale"
],
- "description": "Get the current user location based on IP. Returns an object with user country code, country name, continent name, continent code, ip address and suggested currency. You can use the locale header to get the data in a supported language.\n\n([IP Geolocation by DB-IP](https:\/\/db-ip.com))",
+ "description": "Get the current user location based on IP. Returns an object with user country code, country name, continent name, continent code, ip address and suggested currency. You can use the locale header to get the data in a supported language.\r\n\r\n([IP Geolocation by DB-IP](https:\/\/db-ip.com))",
"responses": {
"200": {
"description": "Locale",
@@ -13464,7 +13464,7 @@
},
"\/locale\/codes": {
"get": {
- "summary": "List Locale Codes",
+ "summary": "List locale codes",
"operationId": "localeListCodes",
"consumes": [
"application\/json"
@@ -14104,7 +14104,7 @@
"tags": [
"messaging"
],
- "description": "Update an email message by its unique ID.\n",
+ "description": "Update an email message by its unique ID.\r\n",
"responses": {
"200": {
"description": "Message",
@@ -14436,7 +14436,7 @@
"tags": [
"messaging"
],
- "description": "Update a push notification by its unique ID.\n",
+ "description": "Update a push notification by its unique ID.\r\n",
"responses": {
"200": {
"description": "Message",
@@ -14728,7 +14728,7 @@
"tags": [
"messaging"
],
- "description": "Update an email message by its unique ID.\n",
+ "description": "Update an email message by its unique ID.\r\n",
"responses": {
"200": {
"description": "Message",
@@ -14846,7 +14846,7 @@
"tags": [
"messaging"
],
- "description": "Get a message by its unique ID.\n",
+ "description": "Get a message by its unique ID.\r\n",
"responses": {
"200": {
"description": "Message",
@@ -17461,7 +17461,7 @@
"tags": [
"messaging"
],
- "description": "Get a provider by its unique ID.\n",
+ "description": "Get a provider by its unique ID.\r\n",
"responses": {
"200": {
"description": "Provider",
@@ -17903,7 +17903,7 @@
"tags": [
"messaging"
],
- "description": "Get a topic by its unique ID.\n",
+ "description": "Get a topic by its unique ID.\r\n",
"responses": {
"200": {
"description": "Topic",
@@ -17965,7 +17965,7 @@
"tags": [
"messaging"
],
- "description": "Update a topic by its unique ID.\n",
+ "description": "Update a topic by its unique ID.\r\n",
"responses": {
"200": {
"description": "Topic",
@@ -18363,7 +18363,7 @@
"tags": [
"messaging"
],
- "description": "Get a subscriber by its unique ID.\n",
+ "description": "Get a subscriber by its unique ID.\r\n",
"responses": {
"200": {
"description": "Subscriber",
@@ -18494,7 +18494,7 @@
},
"\/migrations": {
"get": {
- "summary": "List Migrations",
+ "summary": "List migrations",
"operationId": "migrationsList",
"consumes": [
"application\/json"
@@ -18569,7 +18569,7 @@
},
"\/migrations\/appwrite": {
"post": {
- "summary": "Migrate Appwrite Data",
+ "summary": "Migrate Appwrite data",
"operationId": "migrationsCreateAppwriteMigration",
"consumes": [
"application\/json"
@@ -18665,7 +18665,7 @@
},
"\/migrations\/appwrite\/report": {
"get": {
- "summary": "Generate a report on Appwrite Data",
+ "summary": "Generate a report on Appwrite data",
"operationId": "migrationsGetAppwriteReport",
"consumes": [
"application\/json"
@@ -18755,7 +18755,7 @@
},
"\/migrations\/firebase": {
"post": {
- "summary": "Migrate Firebase Data (Service Account)",
+ "summary": "Migrate Firebase data (Service Account)",
"operationId": "migrationsCreateFirebaseMigration",
"consumes": [
"application\/json"
@@ -18837,7 +18837,7 @@
},
"\/migrations\/firebase\/deauthorize": {
"get": {
- "summary": "Revoke Appwrite's authorization to access Firebase Projects",
+ "summary": "Revoke Appwrite's authorization to access Firebase projects",
"operationId": "migrationsDeleteFirebaseAuth",
"consumes": [
"application\/json"
@@ -18889,7 +18889,7 @@
},
"\/migrations\/firebase\/oauth": {
"post": {
- "summary": "Migrate Firebase Data (OAuth)",
+ "summary": "Migrate Firebase data (OAuth)",
"operationId": "migrationsCreateFirebaseOAuthMigration",
"consumes": [
"application\/json"
@@ -18971,7 +18971,7 @@
},
"\/migrations\/firebase\/projects": {
"get": {
- "summary": "List Firebase Projects",
+ "summary": "List Firebase projects",
"operationId": "migrationsListFirebaseProjects",
"consumes": [
"application\/json"
@@ -19023,7 +19023,7 @@
},
"\/migrations\/firebase\/report": {
"get": {
- "summary": "Generate a report on Firebase Data",
+ "summary": "Generate a report on Firebase data",
"operationId": "migrationsGetFirebaseReport",
"consumes": [
"application\/json"
@@ -19096,7 +19096,7 @@
},
"\/migrations\/firebase\/report\/oauth": {
"get": {
- "summary": "Generate a report on Firebase Data using OAuth",
+ "summary": "Generate a report on Firebase data using OAuth",
"operationId": "migrationsGetFirebaseReportOAuth",
"consumes": [
"application\/json"
@@ -19169,7 +19169,7 @@
},
"\/migrations\/nhost": {
"post": {
- "summary": "Migrate NHost Data",
+ "summary": "Migrate NHost data",
"operationId": "migrationsCreateNHostMigration",
"consumes": [
"application\/json"
@@ -19414,7 +19414,7 @@
},
"\/migrations\/supabase": {
"post": {
- "summary": "Migrate Supabase Data",
+ "summary": "Migrate Supabase data",
"operationId": "migrationsCreateSupabaseMigration",
"consumes": [
"application\/json"
@@ -19645,7 +19645,7 @@
},
"\/migrations\/{migrationId}": {
"get": {
- "summary": "Get Migration",
+ "summary": "Get migration",
"operationId": "migrationsGet",
"consumes": [
"application\/json"
@@ -19705,7 +19705,7 @@
]
},
"patch": {
- "summary": "Retry Migration",
+ "summary": "Retry migration",
"operationId": "migrationsRetry",
"consumes": [
"application\/json"
@@ -19765,7 +19765,7 @@
]
},
"delete": {
- "summary": "Delete Migration",
+ "summary": "Delete migration",
"operationId": "migrationsDelete",
"consumes": [
"application\/json"
@@ -19908,7 +19908,7 @@
},
"\/project\/variables": {
"get": {
- "summary": "List Variables",
+ "summary": "List variables",
"operationId": "projectListVariables",
"consumes": [
"application\/json"
@@ -19958,7 +19958,7 @@
]
},
"post": {
- "summary": "Create Variable",
+ "summary": "Create variable",
"operationId": "projectCreateVariable",
"consumes": [
"application\/json"
@@ -20037,7 +20037,7 @@
},
"\/project\/variables\/{variableId}": {
"get": {
- "summary": "Get Variable",
+ "summary": "Get variable",
"operationId": "projectGetVariable",
"consumes": [
"application\/json"
@@ -20097,7 +20097,7 @@
]
},
"put": {
- "summary": "Update Variable",
+ "summary": "Update variable",
"operationId": "projectUpdateVariable",
"consumes": [
"application\/json"
@@ -20181,7 +20181,7 @@
]
},
"delete": {
- "summary": "Delete Variable",
+ "summary": "Delete variable",
"operationId": "projectDeleteVariable",
"consumes": [
"application\/json"
@@ -21748,7 +21748,7 @@
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
- "scope": "projects.read",
+ "scope": "keys.read",
"platforms": [
"console"
],
@@ -21808,7 +21808,7 @@
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
- "scope": "projects.write",
+ "scope": "keys.write",
"platforms": [
"console"
],
@@ -21904,7 +21904,7 @@
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
- "scope": "projects.read",
+ "scope": "keys.read",
"platforms": [
"console"
],
@@ -21972,7 +21972,7 @@
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
- "scope": "projects.write",
+ "scope": "keys.write",
"platforms": [
"console"
],
@@ -22069,7 +22069,7 @@
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
- "scope": "projects.write",
+ "scope": "keys.write",
"platforms": [
"console"
],
@@ -22280,7 +22280,7 @@
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
- "scope": "projects.read",
+ "scope": "platforms.read",
"platforms": [
"console"
],
@@ -22340,7 +22340,7 @@
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
- "scope": "projects.write",
+ "scope": "platforms.write",
"platforms": [
"console"
],
@@ -22464,7 +22464,7 @@
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
- "scope": "projects.read",
+ "scope": "platforms.read",
"platforms": [
"console"
],
@@ -22532,7 +22532,7 @@
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
- "scope": "projects.write",
+ "scope": "platforms.write",
"platforms": [
"console"
],
@@ -22631,7 +22631,7 @@
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
- "scope": "projects.write",
+ "scope": "platforms.write",
"platforms": [
"console"
],
@@ -23101,7 +23101,8 @@
"default": "",
"x-example": "tls",
"enum": [
- "tls"
+ "tls",
+ "ssl"
],
"x-enum-name": "SMTPSecure",
"x-enum-keys": []
@@ -25101,7 +25102,7 @@
},
"\/proxy\/rules": {
"get": {
- "summary": "List Rules",
+ "summary": "List rules",
"operationId": "proxyListRules",
"consumes": [
"application\/json"
@@ -25174,7 +25175,7 @@
]
},
"post": {
- "summary": "Create Rule",
+ "summary": "Create rule",
"operationId": "proxyCreateRule",
"consumes": [
"application\/json"
@@ -25265,7 +25266,7 @@
},
"\/proxy\/rules\/{ruleId}": {
"get": {
- "summary": "Get Rule",
+ "summary": "Get rule",
"operationId": "proxyGetRule",
"consumes": [
"application\/json"
@@ -25325,7 +25326,7 @@
]
},
"delete": {
- "summary": "Delete Rule",
+ "summary": "Delete rule",
"operationId": "proxyDeleteRule",
"consumes": [
"application\/json"
@@ -25382,7 +25383,7 @@
},
"\/proxy\/rules\/{ruleId}\/verification": {
"patch": {
- "summary": "Update Rule Verification Status",
+ "summary": "Update rule verification status",
"operationId": "proxyUpdateRuleVerification",
"consumes": [
"application\/json"
@@ -26014,7 +26015,7 @@
"tags": [
"storage"
],
- "description": "Create a new file. Before using this route, you should create a new bucket resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/storage#storageCreateBucket) API or directly from your Appwrite console.\n\nLarger files should be uploaded using multiple requests with the [content-range](https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Content-Range) header to send a partial request with a maximum supported chunk of `5MB`. The `content-range` header values should always be in bytes.\n\nWhen the first request is sent, the server will return the **File** object, and the subsequent part request must include the file's **id** in `x-appwrite-id` header to allow the server to know that the partial upload is for the existing file and not for a new one.\n\nIf you're creating a new file using one of the Appwrite SDKs, all the chunking logic will be managed by the SDK internally.\n",
+ "description": "Create a new file. Before using this route, you should create a new bucket resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/storage#storageCreateBucket) API or directly from your Appwrite console.\r\n\r\nLarger files should be uploaded using multiple requests with the [content-range](https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Content-Range) header to send a partial request with a maximum supported chunk of `5MB`. The `content-range` header values should always be in bytes.\r\n\r\nWhen the first request is sent, the server will return the **File** object, and the subsequent part request must include the file's **id** in `x-appwrite-id` header to allow the server to know that the partial upload is for the existing file and not for a new one.\r\n\r\nIf you're creating a new file using one of the Appwrite SDKs, all the chunking logic will be managed by the SDK internally.\r\n",
"responses": {
"201": {
"description": "File",
@@ -26265,7 +26266,7 @@
]
},
"delete": {
- "summary": "Delete File",
+ "summary": "Delete file",
"operationId": "storageDeleteFile",
"consumes": [
"application\/json"
@@ -26596,7 +26597,8 @@
"jpeg",
"gif",
"png",
- "webp"
+ "webp",
+ "avif"
],
"x-enum-name": "ImageFormat",
"x-enum-keys": [],
@@ -27386,7 +27388,7 @@
"tags": [
"teams"
],
- "description": "Invite a new member to join your team. Provide an ID for existing users, or invite unregistered users using an email or phone number. If initiated from a Client SDK, Appwrite will send an email or sms with a link to join the team to the invited user, and an account will be created for them if one doesn't exist. If initiated from a Server SDK, the new member will be added automatically to the team.\n\nYou only need to provide one of a user ID, email, or phone number. Appwrite will prioritize accepting the user ID > email > phone number if you provide more than one of these parameters.\n\nUse the `url` parameter to redirect the user from the invitation email to your app. After the user is redirected, use the [Update Team Membership Status](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/teams#updateMembershipStatus) endpoint to allow the user to accept the invitation to the team. \n\nPlease note that to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) Appwrite will accept the only redirect URLs under the domains you have added as a platform on the Appwrite Console.\n",
+ "description": "Invite a new member to join your team. Provide an ID for existing users, or invite unregistered users using an email or phone number. If initiated from a Client SDK, Appwrite will send an email or sms with a link to join the team to the invited user, and an account will be created for them if one doesn't exist. If initiated from a Server SDK, the new member will be added automatically to the team.\r\n\r\nYou only need to provide one of a user ID, email, or phone number. Appwrite will prioritize accepting the user ID > email > phone number if you provide more than one of these parameters.\r\n\r\nUse the `url` parameter to redirect the user from the invitation email to your app. After the user is redirected, use the [Update Team Membership Status](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/teams#updateMembershipStatus) endpoint to allow the user to accept the invitation to the team. \r\n\r\nPlease note that to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) Appwrite will accept the only redirect URLs under the domains you have added as a platform on the Appwrite Console.\r\n",
"responses": {
"201": {
"description": "Membership",
@@ -27575,7 +27577,7 @@
"tags": [
"teams"
],
- "description": "Modify the roles of a team member. Only team members with the owner role have access to this endpoint. Learn more about [roles and permissions](https:\/\/appwrite.io\/docs\/permissions).\n",
+ "description": "Modify the roles of a team member. Only team members with the owner role have access to this endpoint. Learn more about [roles and permissions](https:\/\/appwrite.io\/docs\/permissions).\r\n",
"responses": {
"200": {
"description": "Membership",
@@ -27737,7 +27739,7 @@
"tags": [
"teams"
],
- "description": "Use this endpoint to allow a user to accept an invitation to join a team after being redirected back to your app from the invitation email received by the user.\n\nIf the request is successful, a session for the user is automatically created.\n",
+ "description": "Use this endpoint to allow a user to accept an invitation to join a team after being redirected back to your app from the invitation email received by the user.\r\n\r\nIf the request is successful, a session for the user is automatically created.\r\n",
"responses": {
"200": {
"description": "Membership",
@@ -28324,7 +28326,7 @@
},
"\/users\/identities": {
"get": {
- "summary": "List Identities",
+ "summary": "List identities",
"operationId": "usersListIdentities",
"consumes": [
"application\/json"
@@ -29369,7 +29371,7 @@
"tags": [
"users"
],
- "description": "Update the user labels by its unique ID. \n\nLabels can be used to grant access to resources. While teams are a way for user's to share access to a resource, labels can be defined by the developer to grant access without an invitation. See the [Permissions docs](https:\/\/appwrite.io\/docs\/permissions) for more info.",
+ "description": "Update the user labels by its unique ID. \r\n\r\nLabels can be used to grant access to resources. While teams are a way for user's to share access to a resource, labels can be defined by the developer to grant access without an invitation. See the [Permissions docs](https:\/\/appwrite.io\/docs\/permissions) for more info.",
"responses": {
"200": {
"description": "User",
@@ -29661,7 +29663,7 @@
},
"\/users\/{userId}\/mfa\/authenticators\/{type}": {
"delete": {
- "summary": "Delete Authenticator",
+ "summary": "Delete authenticator",
"operationId": "usersDeleteMfaAuthenticator",
"consumes": [
"application\/json"
@@ -29737,7 +29739,7 @@
},
"\/users\/{userId}\/mfa\/factors": {
"get": {
- "summary": "List Factors",
+ "summary": "List factors",
"operationId": "usersListMfaFactors",
"consumes": [
"application\/json"
@@ -29800,7 +29802,7 @@
},
"\/users\/{userId}\/mfa\/recovery-codes": {
"get": {
- "summary": "Get MFA Recovery Codes",
+ "summary": "Get MFA recovery codes",
"operationId": "usersGetMfaRecoveryCodes",
"consumes": [
"application\/json"
@@ -29861,7 +29863,7 @@
]
},
"put": {
- "summary": "Regenerate MFA Recovery Codes",
+ "summary": "Regenerate MFA recovery codes",
"operationId": "usersUpdateMfaRecoveryCodes",
"consumes": [
"application\/json"
@@ -29922,7 +29924,7 @@
]
},
"patch": {
- "summary": "Create MFA Recovery Codes",
+ "summary": "Create MFA recovery codes",
"operationId": "usersCreateMfaRecoveryCodes",
"consumes": [
"application\/json"
@@ -30442,7 +30444,7 @@
"tags": [
"users"
],
- "description": "Creates a session for a user. Returns an immediately usable session object.\n\nIf you want to generate a token for a custom authentication flow, use the [POST \/users\/{userId}\/tokens](https:\/\/appwrite.io\/docs\/server\/users#createToken) endpoint.",
+ "description": "Creates a session for a user. Returns an immediately usable session object.\r\n\r\nIf you want to generate a token for a custom authentication flow, use the [POST \/users\/{userId}\/tokens](https:\/\/appwrite.io\/docs\/server\/users#createToken) endpoint.",
"responses": {
"201": {
"description": "Session",
@@ -30697,7 +30699,7 @@
},
"\/users\/{userId}\/targets": {
"get": {
- "summary": "List User Targets",
+ "summary": "List user targets",
"operationId": "usersListTargets",
"consumes": [
"application\/json"
@@ -30771,7 +30773,7 @@
]
},
"post": {
- "summary": "Create User Target",
+ "summary": "Create user target",
"operationId": "usersCreateTarget",
"consumes": [
"application\/json"
@@ -30886,7 +30888,7 @@
},
"\/users\/{userId}\/targets\/{targetId}": {
"get": {
- "summary": "Get User Target",
+ "summary": "Get user target",
"operationId": "usersGetTarget",
"consumes": [
"application\/json"
@@ -30956,7 +30958,7 @@
]
},
"patch": {
- "summary": "Update User target",
+ "summary": "Update user target",
"operationId": "usersUpdateTarget",
"consumes": [
"application\/json"
@@ -31133,7 +31135,7 @@
"tags": [
"users"
],
- "description": "Returns a token with a secret key for creating a session. Use the user ID and secret and submit a request to the [PUT \/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process.\n",
+ "description": "Returns a token with a secret key for creating a session. Use the user ID and secret and submit a request to the [PUT \/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process.\r\n",
"responses": {
"201": {
"description": "Token",
@@ -31368,7 +31370,7 @@
},
"\/vcs\/github\/installations\/{installationId}\/providerRepositories": {
"get": {
- "summary": "List Repositories",
+ "summary": "List repositories",
"operationId": "vcsListRepositories",
"consumes": [
"application\/json"
@@ -31594,7 +31596,7 @@
},
"\/vcs\/github\/installations\/{installationId}\/providerRepositories\/{providerRepositoryId}\/branches": {
"get": {
- "summary": "List Repository Branches",
+ "summary": "List repository branches",
"operationId": "vcsListRepositoryBranches",
"consumes": [
"application\/json"
@@ -32046,7 +32048,7 @@
]
},
"delete": {
- "summary": "Delete Installation",
+ "summary": "Delete installation",
"operationId": "vcsDeleteInstallation",
"consumes": [
"application\/json"
@@ -33456,6 +33458,16 @@
"x-example": false,
"x-nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"size": {
"type": "integer",
"description": "Attribute size.",
@@ -33475,6 +33487,8 @@
"status",
"error",
"required",
+ "$createdAt",
+ "$updatedAt",
"size"
]
},
@@ -33513,6 +33527,16 @@
"x-example": false,
"x-nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"min": {
"type": "integer",
"description": "Minimum value to enforce for new documents.",
@@ -33540,7 +33564,9 @@
"type",
"status",
"error",
- "required"
+ "required",
+ "$createdAt",
+ "$updatedAt"
]
},
"attributeFloat": {
@@ -33578,6 +33604,16 @@
"x-example": false,
"x-nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"min": {
"type": "number",
"description": "Minimum value to enforce for new documents.",
@@ -33605,7 +33641,9 @@
"type",
"status",
"error",
- "required"
+ "required",
+ "$createdAt",
+ "$updatedAt"
]
},
"attributeBoolean": {
@@ -33643,6 +33681,16 @@
"x-example": false,
"x-nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"default": {
"type": "boolean",
"description": "Default value for attribute when not provided. Cannot be set when attribute is required.",
@@ -33655,7 +33703,9 @@
"type",
"status",
"error",
- "required"
+ "required",
+ "$createdAt",
+ "$updatedAt"
]
},
"attributeEmail": {
@@ -33693,6 +33743,16 @@
"x-example": false,
"x-nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"format": {
"type": "string",
"description": "String format.",
@@ -33711,6 +33771,8 @@
"status",
"error",
"required",
+ "$createdAt",
+ "$updatedAt",
"format"
]
},
@@ -33749,6 +33811,16 @@
"x-example": false,
"x-nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"elements": {
"type": "array",
"description": "Array of elements in enumerated type.",
@@ -33775,6 +33847,8 @@
"status",
"error",
"required",
+ "$createdAt",
+ "$updatedAt",
"elements",
"format"
]
@@ -33814,6 +33888,16 @@
"x-example": false,
"x-nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"format": {
"type": "string",
"description": "String format.",
@@ -33832,6 +33916,8 @@
"status",
"error",
"required",
+ "$createdAt",
+ "$updatedAt",
"format"
]
},
@@ -33870,6 +33956,16 @@
"x-example": false,
"x-nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"format": {
"type": "string",
"description": "String format.",
@@ -33888,6 +33984,8 @@
"status",
"error",
"required",
+ "$createdAt",
+ "$updatedAt",
"format"
]
},
@@ -33926,6 +34024,16 @@
"x-example": false,
"x-nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"format": {
"type": "string",
"description": "ISO 8601 format.",
@@ -33944,6 +34052,8 @@
"status",
"error",
"required",
+ "$createdAt",
+ "$updatedAt",
"format"
]
},
@@ -33982,6 +34092,16 @@
"x-example": false,
"x-nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"relatedCollection": {
"type": "string",
"description": "The ID of the related collection.",
@@ -34019,6 +34139,8 @@
"status",
"error",
"required",
+ "$createdAt",
+ "$updatedAt",
"relatedCollection",
"relationType",
"twoWay",
@@ -34067,6 +34189,16 @@
},
"x-example": [],
"x-nullable": true
+ },
+ "$createdAt": {
+ "type": "string",
+ "description": "Index creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Index update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
}
},
"required": [
@@ -34074,7 +34206,9 @@
"type",
"status",
"error",
- "attributes"
+ "attributes",
+ "$createdAt",
+ "$updatedAt"
]
},
"document": {
@@ -36104,9 +36238,9 @@
"format": "int32"
},
"responseBody": {
- "type": "string",
+ "type": "payload",
"description": "HTTP response body. This will return empty unless execution is created as synchronous.",
- "x-example": "Developers are awesome."
+ "x-example": ""
},
"responseHeaders": {
"type": "array",
@@ -37270,6 +37404,12 @@
"x-example": 0,
"format": "int32"
},
+ "storageTotal": {
+ "type": "integer",
+ "description": "Total aggregated number of total databases storage in bytes.",
+ "x-example": 0,
+ "format": "int32"
+ },
"databases": {
"type": "array",
"description": "Aggregated number of databases per period.",
@@ -37296,6 +37436,15 @@
"$ref": "#\/definitions\/metric"
},
"x-example": []
+ },
+ "storage": {
+ "type": "array",
+ "description": "An array of the aggregated number of databases storage in bytes per period.",
+ "items": {
+ "type": "object",
+ "$ref": "#\/definitions\/metric"
+ },
+ "x-example": []
}
},
"required": [
@@ -37303,9 +37452,11 @@
"databasesTotal",
"collectionsTotal",
"documentsTotal",
+ "storageTotal",
"databases",
"collections",
- "documents"
+ "documents",
+ "storage"
]
},
"usageDatabase": {
@@ -37329,6 +37480,12 @@
"x-example": 0,
"format": "int32"
},
+ "storageTotal": {
+ "type": "integer",
+ "description": "Total aggregated number of total storage used in bytes.",
+ "x-example": 0,
+ "format": "int32"
+ },
"collections": {
"type": "array",
"description": "Aggregated number of collections per period.",
@@ -37346,14 +37503,25 @@
"$ref": "#\/definitions\/metric"
},
"x-example": []
+ },
+ "storage": {
+ "type": "array",
+ "description": "Aggregated storage used in bytes per period.",
+ "items": {
+ "type": "object",
+ "$ref": "#\/definitions\/metric"
+ },
+ "x-example": []
}
},
"required": [
"range",
"collectionsTotal",
"documentsTotal",
+ "storageTotal",
"collections",
- "documents"
+ "documents",
+ "storage"
]
},
"usageCollection": {
@@ -37921,6 +38089,12 @@
"x-example": 0,
"format": "int32"
},
+ "databasesStorageTotal": {
+ "type": "integer",
+ "description": "Total aggregated sum of databases storage size (in bytes).",
+ "x-example": 0,
+ "format": "int32"
+ },
"usersTotal": {
"type": "integer",
"description": "Total aggregated number of users.",
@@ -38023,6 +38197,15 @@
},
"x-example": []
},
+ "databasesStorageBreakdown": {
+ "type": "array",
+ "description": "An array of the aggregated breakdown of storage usage by databases.",
+ "items": {
+ "type": "object",
+ "$ref": "#\/definitions\/metricBreakdown"
+ },
+ "x-example": []
+ },
"executionsMbSecondsBreakdown": {
"type": "array",
"description": "Aggregated breakdown in totals of execution mbSeconds by functions.",
@@ -38055,6 +38238,7 @@
"executionsTotal",
"documentsTotal",
"databasesTotal",
+ "databasesStorageTotal",
"usersTotal",
"filesStorageTotal",
"functionsStorageTotal",
@@ -38069,6 +38253,7 @@
"executions",
"executionsBreakdown",
"bucketsBreakdown",
+ "databasesStorageBreakdown",
"executionsMbSecondsBreakdown",
"buildsMbSecondsBreakdown",
"functionsStorageBreakdown"
diff --git a/app/config/specs/swagger2-1.6.x-server.json b/app/config/specs/swagger2-1.6.x-server.json
index af6274226f..80c9e6037f 100644
--- a/app/config/specs/swagger2-1.6.x-server.json
+++ b/app/config/specs/swagger2-1.6.x-server.json
@@ -238,7 +238,7 @@
"tags": [
"account"
],
- "description": "Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. For security measures, user password is required to complete this request.\nThis endpoint can also be used to convert an anonymous account to a normal one, by passing an email address and a new password.\n",
+ "description": "Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. For security measures, user password is required to complete this request.\r\nThis endpoint can also be used to convert an anonymous account to a normal one, by passing an email address and a new password.\r\n",
"responses": {
"200": {
"description": "User",
@@ -310,7 +310,7 @@
},
"\/account\/identities": {
"get": {
- "summary": "List Identities",
+ "summary": "List identities",
"operationId": "accountListIdentities",
"consumes": [
"application\/json"
@@ -640,7 +640,7 @@
},
"\/account\/mfa\/authenticators\/{type}": {
"post": {
- "summary": "Create Authenticator",
+ "summary": "Create authenticator",
"operationId": "accountCreateMfaAuthenticator",
"consumes": [
"application\/json"
@@ -709,7 +709,7 @@
]
},
"put": {
- "summary": "Verify Authenticator",
+ "summary": "Verify authenticator",
"operationId": "accountUpdateMfaAuthenticator",
"consumes": [
"application\/json"
@@ -796,7 +796,7 @@
]
},
"delete": {
- "summary": "Delete Authenticator",
+ "summary": "Delete authenticator",
"operationId": "accountDeleteMfaAuthenticator",
"consumes": [
"application\/json"
@@ -862,7 +862,7 @@
},
"\/account\/mfa\/challenge": {
"post": {
- "summary": "Create MFA Challenge",
+ "summary": "Create MFA challenge",
"operationId": "accountCreateMfaChallenge",
"consumes": [
"application\/json"
@@ -941,7 +941,7 @@
]
},
"put": {
- "summary": "Create MFA Challenge (confirmation)",
+ "summary": "Create MFA challenge (confirmation)",
"operationId": "accountUpdateMfaChallenge",
"consumes": [
"application\/json"
@@ -1019,7 +1019,7 @@
},
"\/account\/mfa\/factors": {
"get": {
- "summary": "List Factors",
+ "summary": "List factors",
"operationId": "accountListMfaFactors",
"consumes": [
"application\/json"
@@ -1075,7 +1075,7 @@
},
"\/account\/mfa\/recovery-codes": {
"get": {
- "summary": "Get MFA Recovery Codes",
+ "summary": "Get MFA recovery codes",
"operationId": "accountGetMfaRecoveryCodes",
"consumes": [
"application\/json"
@@ -1129,7 +1129,7 @@
]
},
"post": {
- "summary": "Create MFA Recovery Codes",
+ "summary": "Create MFA recovery codes",
"operationId": "accountCreateMfaRecoveryCodes",
"consumes": [
"application\/json"
@@ -1183,7 +1183,7 @@
]
},
"patch": {
- "summary": "Regenerate MFA Recovery Codes",
+ "summary": "Regenerate MFA recovery codes",
"operationId": "accountUpdateMfaRecoveryCodes",
"consumes": [
"application\/json"
@@ -1705,7 +1705,7 @@
"tags": [
"account"
],
- "description": "Use this endpoint to complete the user account password reset. Both the **userId** and **secret** arguments will be passed as query parameters to the redirect URL you have provided when sending your request to the [POST \/account\/recovery](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createRecovery) endpoint.\n\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.",
+ "description": "Use this endpoint to complete the user account password reset. Both the **userId** and **secret** arguments will be passed as query parameters to the redirect URL you have provided when sending your request to the [POST \/account\/recovery](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createRecovery) endpoint.\r\n\r\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.",
"responses": {
"200": {
"description": "Token",
@@ -1953,7 +1953,7 @@
"tags": [
"account"
],
- "description": "Allow the user to login into their account by providing a valid email and password combination. This route will create a new session for the user.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
+ "description": "Allow the user to login into their account by providing a valid email and password combination. This route will create a new session for the user.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
"responses": {
"201": {
"description": "Session",
@@ -2518,7 +2518,7 @@
"tags": [
"account"
],
- "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's email is valid for 15 minutes.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
+ "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's email is valid for 15 minutes.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
"responses": {
"201": {
"description": "Token",
@@ -2604,7 +2604,7 @@
"tags": [
"account"
],
- "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour. If you are on a mobile device you can leave the URL parameter empty, so that the login completion will be handled by your Appwrite instance by default.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\n",
+ "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour. If you are on a mobile device you can leave the URL parameter empty, so that the login completion will be handled by your Appwrite instance by default.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\r\n",
"responses": {
"201": {
"description": "Token",
@@ -2699,7 +2699,7 @@
"tags": [
"account"
],
- "description": "Allow the user to login to their account using the OAuth2 provider of their choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed. \n\nIf authentication succeeds, `userId` and `secret` of a token will be appended to the success URL as query parameters. These can be used to create a new session using the [Create session](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
+ "description": "Allow the user to login to their account using the OAuth2 provider of their choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed. \r\n\r\nIf authentication succeeds, `userId` and `secret` of a token will be appended to the success URL as query parameters. These can be used to create a new session using the [Create session](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
"responses": {
"301": {
"description": "No content"
@@ -2834,7 +2834,7 @@
"tags": [
"account"
],
- "description": "Sends the user an SMS with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's phone is valid for 15 minutes.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
+ "description": "Sends the user an SMS with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's phone is valid for 15 minutes.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
"responses": {
"201": {
"description": "Token",
@@ -2917,7 +2917,7 @@
"tags": [
"account"
],
- "description": "Use this endpoint to send a verification message to your user email address to confirm they are the valid owners of that address. Both the **userId** and **secret** arguments will be passed as query parameters to the URL you have provided to be attached to the verification email. The provided URL should redirect the user back to your app and allow you to complete the verification process by verifying both the **userId** and **secret** parameters. Learn more about how to [complete the verification process](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\n\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.\n",
+ "description": "Use this endpoint to send a verification message to your user email address to confirm they are the valid owners of that address. Both the **userId** and **secret** arguments will be passed as query parameters to the URL you have provided to be attached to the verification email. The provided URL should redirect the user back to your app and allow you to complete the verification process by verifying both the **userId** and **secret** parameters. Learn more about how to [complete the verification process](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\r\n\r\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.\r\n",
"responses": {
"201": {
"description": "Token",
@@ -3214,7 +3214,7 @@
"tags": [
"avatars"
],
- "description": "You can use this endpoint to show different browser icons to your users. The code argument receives the browser code as it appears in your user [GET \/account\/sessions](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#getSessions) endpoint. Use width, height and quality arguments to change the output settings.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.",
+ "description": "You can use this endpoint to show different browser icons to your users. The code argument receives the browser code as it appears in your user [GET \/account\/sessions](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#getSessions) endpoint. Use width, height and quality arguments to change the output settings.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.",
"responses": {
"200": {
"description": "Image",
@@ -3345,7 +3345,7 @@
"tags": [
"avatars"
],
- "description": "The credit card endpoint will return you the icon of the credit card provider you need. Use width, height and quality arguments to change the output settings.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n",
+ "description": "The credit card endpoint will return you the icon of the credit card provider you need. Use width, height and quality arguments to change the output settings.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n",
"responses": {
"200": {
"description": "Image",
@@ -3480,7 +3480,7 @@
"tags": [
"avatars"
],
- "description": "Use this endpoint to fetch the favorite icon (AKA favicon) of any remote website URL.\n\nThis endpoint does not follow HTTP redirects.",
+ "description": "Use this endpoint to fetch the favorite icon (AKA favicon) of any remote website URL.\r\n\r\nThis endpoint does not follow HTTP redirects.",
"responses": {
"200": {
"description": "Image",
@@ -3549,7 +3549,7 @@
"tags": [
"avatars"
],
- "description": "You can use this endpoint to show different country flags icons to your users. The code argument receives the 2 letter country code. Use width, height and quality arguments to change the output settings. Country codes follow the [ISO 3166-1](https:\/\/en.wikipedia.org\/wiki\/ISO_3166-1) standard.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n",
+ "description": "You can use this endpoint to show different country flags icons to your users. The code argument receives the 2 letter country code. Use width, height and quality arguments to change the output settings. Country codes follow the [ISO 3166-1](https:\/\/en.wikipedia.org\/wiki\/ISO_3166-1) standard.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n",
"responses": {
"200": {
"description": "Image",
@@ -4042,7 +4042,7 @@
"tags": [
"avatars"
],
- "description": "Use this endpoint to fetch a remote image URL and crop it to any image size you want. This endpoint is very useful if you need to crop and display remote images in your app or in case you want to make sure a 3rd party image is properly served using a TLS protocol.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 400x400px.\n\nThis endpoint does not follow HTTP redirects.",
+ "description": "Use this endpoint to fetch a remote image URL and crop it to any image size you want. This endpoint is very useful if you need to crop and display remote images in your app or in case you want to make sure a 3rd party image is properly served using a TLS protocol.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 400x400px.\r\n\r\nThis endpoint does not follow HTTP redirects.",
"responses": {
"200": {
"description": "Image",
@@ -4131,7 +4131,7 @@
"tags": [
"avatars"
],
- "description": "Use this endpoint to show your user initials avatar icon on your website or app. By default, this route will try to print your logged-in user name or email initials. You can also overwrite the user name if you pass the 'name' parameter. If no name is given and no user is logged, an empty avatar will be returned.\n\nYou can use the color and background params to change the avatar colors. By default, a random theme will be selected. The random theme will persist for the user's initials when reloading the same theme will always return for the same initials.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n",
+ "description": "Use this endpoint to show your user initials avatar icon on your website or app. By default, this route will try to print your logged-in user name or email initials. You can also overwrite the user name if you pass the 'name' parameter. If no name is given and no user is logged, an empty avatar will be returned.\r\n\r\nYou can use the color and background params to change the avatar colors. By default, a random theme will be selected. The random theme will persist for the user's initials when reloading the same theme will always return for the same initials.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n",
"responses": {
"200": {
"description": "Image",
@@ -4228,7 +4228,7 @@
"tags": [
"avatars"
],
- "description": "Converts a given plain text to a QR code image. You can use the query parameters to change the size and style of the resulting image.\n",
+ "description": "Converts a given plain text to a QR code image. You can use the query parameters to change the size and style of the resulting image.\r\n",
"responses": {
"200": {
"description": "Image",
@@ -4400,7 +4400,7 @@
"tags": [
"databases"
],
- "description": "Create a new Database.\n",
+ "description": "Create a new Database.\r\n",
"responses": {
"201": {
"description": "Database",
@@ -5217,7 +5217,7 @@
"tags": [
"databases"
],
- "description": "Create a boolean attribute.\n",
+ "description": "Create a boolean attribute.\r\n",
"responses": {
"202": {
"description": "AttributeBoolean",
@@ -5653,7 +5653,7 @@
"tags": [
"databases"
],
- "description": "Create an email attribute.\n",
+ "description": "Create an email attribute.\r\n",
"responses": {
"202": {
"description": "AttributeEmail",
@@ -5760,7 +5760,7 @@
"tags": [
"databases"
],
- "description": "Update an email attribute. Changing the `default` value will not update already existing documents.\n",
+ "description": "Update an email attribute. Changing the `default` value will not update already existing documents.\r\n",
"responses": {
"200": {
"description": "AttributeEmail",
@@ -5871,7 +5871,7 @@
"tags": [
"databases"
],
- "description": "Create an enumeration attribute. The `elements` param acts as a white-list of accepted values for this attribute. \n",
+ "description": "Create an enumeration attribute. The `elements` param acts as a white-list of accepted values for this attribute. \r\n",
"responses": {
"202": {
"description": "AttributeEnum",
@@ -5988,7 +5988,7 @@
"tags": [
"databases"
],
- "description": "Update an enum attribute. Changing the `default` value will not update already existing documents.\n",
+ "description": "Update an enum attribute. Changing the `default` value will not update already existing documents.\r\n",
"responses": {
"200": {
"description": "AttributeEnum",
@@ -6109,7 +6109,7 @@
"tags": [
"databases"
],
- "description": "Create a float attribute. Optionally, minimum and maximum values can be provided.\n",
+ "description": "Create a float attribute. Optionally, minimum and maximum values can be provided.\r\n",
"responses": {
"202": {
"description": "AttributeFloat",
@@ -6228,7 +6228,7 @@
"tags": [
"databases"
],
- "description": "Update a float attribute. Changing the `default` value will not update already existing documents.\n",
+ "description": "Update a float attribute. Changing the `default` value will not update already existing documents.\r\n",
"responses": {
"200": {
"description": "AttributeFloat",
@@ -6353,7 +6353,7 @@
"tags": [
"databases"
],
- "description": "Create an integer attribute. Optionally, minimum and maximum values can be provided.\n",
+ "description": "Create an integer attribute. Optionally, minimum and maximum values can be provided.\r\n",
"responses": {
"202": {
"description": "AttributeInteger",
@@ -6472,7 +6472,7 @@
"tags": [
"databases"
],
- "description": "Update an integer attribute. Changing the `default` value will not update already existing documents.\n",
+ "description": "Update an integer attribute. Changing the `default` value will not update already existing documents.\r\n",
"responses": {
"200": {
"description": "AttributeInteger",
@@ -6597,7 +6597,7 @@
"tags": [
"databases"
],
- "description": "Create IP address attribute.\n",
+ "description": "Create IP address attribute.\r\n",
"responses": {
"202": {
"description": "AttributeIP",
@@ -6704,7 +6704,7 @@
"tags": [
"databases"
],
- "description": "Update an ip attribute. Changing the `default` value will not update already existing documents.\n",
+ "description": "Update an ip attribute. Changing the `default` value will not update already existing documents.\r\n",
"responses": {
"200": {
"description": "AttributeIP",
@@ -6815,7 +6815,7 @@
"tags": [
"databases"
],
- "description": "Create relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\n",
+ "description": "Create relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\r\n",
"responses": {
"202": {
"description": "AttributeRelationship",
@@ -6951,7 +6951,7 @@
"tags": [
"databases"
],
- "description": "Create a string attribute.\n",
+ "description": "Create a string attribute.\r\n",
"responses": {
"202": {
"description": "AttributeString",
@@ -7071,7 +7071,7 @@
"tags": [
"databases"
],
- "description": "Update a string attribute. Changing the `default` value will not update already existing documents.\n",
+ "description": "Update a string attribute. Changing the `default` value will not update already existing documents.\r\n",
"responses": {
"200": {
"description": "AttributeString",
@@ -7188,7 +7188,7 @@
"tags": [
"databases"
],
- "description": "Create a URL attribute.\n",
+ "description": "Create a URL attribute.\r\n",
"responses": {
"202": {
"description": "AttributeURL",
@@ -7295,7 +7295,7 @@
"tags": [
"databases"
],
- "description": "Update an url attribute. Changing the `default` value will not update already existing documents.\n",
+ "description": "Update an url attribute. Changing the `default` value will not update already existing documents.\r\n",
"responses": {
"200": {
"description": "AttributeURL",
@@ -7586,7 +7586,7 @@
"tags": [
"databases"
],
- "description": "Update relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\n",
+ "description": "Update relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\r\n",
"responses": {
"200": {
"description": "AttributeRelationship",
@@ -8250,7 +8250,7 @@
"tags": [
"databases"
],
- "description": "Creates an index on the attributes listed. Your index should include all the attributes you will query in a single request.\nAttributes can be `key`, `fulltext`, and `unique`.",
+ "description": "Creates an index on the attributes listed. Your index should include all the attributes you will query in a single request.\r\nAttributes can be `key`, `fulltext`, and `unique`.",
"responses": {
"202": {
"description": "Index",
@@ -8917,7 +8917,7 @@
"tags": [
"functions"
],
- "description": "List allowed function specifications for this instance.\n",
+ "description": "List allowed function specifications for this instance.\r\n",
"responses": {
"200": {
"description": "Specifications List",
@@ -9415,7 +9415,7 @@
"tags": [
"functions"
],
- "description": "Create a new function code deployment. Use this endpoint to upload a new version of your code function. To execute your newly uploaded code, you'll need to update the function's deployment to use your new deployment UID.\n\nThis endpoint accepts a tar.gz file compressed with your code. Make sure to include any dependencies your code has within the compressed file. You can learn more about code packaging in the [Appwrite Cloud Functions tutorial](https:\/\/appwrite.io\/docs\/functions).\n\nUse the \"command\" param to set the entrypoint used to execute your code.",
+ "description": "Create a new function code deployment. Use this endpoint to upload a new version of your code function. To execute your newly uploaded code, you'll need to update the function's deployment to use your new deployment UID.\r\n\r\nThis endpoint accepts a tar.gz file compressed with your code. Make sure to include any dependencies your code has within the compressed file. You can learn more about code packaging in the [Appwrite Cloud Functions tutorial](https:\/\/appwrite.io\/docs\/functions).\r\n\r\nUse the \"command\" param to set the entrypoint used to execute your code.",
"responses": {
"202": {
"description": "Deployment",
@@ -10231,7 +10231,7 @@
"tags": [
"functions"
],
- "description": "Delete a function execution by its unique ID.\n",
+ "description": "Delete a function execution by its unique ID.\r\n",
"responses": {
"204": {
"description": "No content"
@@ -11494,7 +11494,7 @@
"tags": [
"health"
],
- "description": "Returns the amount of failed jobs in a given queue.\n",
+ "description": "Returns the amount of failed jobs in a given queue.\r\n",
"responses": {
"200": {
"description": "Health Queue",
@@ -12265,7 +12265,7 @@
"tags": [
"locale"
],
- "description": "Get the current user location based on IP. Returns an object with user country code, country name, continent name, continent code, ip address and suggested currency. You can use the locale header to get the data in a supported language.\n\n([IP Geolocation by DB-IP](https:\/\/db-ip.com))",
+ "description": "Get the current user location based on IP. Returns an object with user country code, country name, continent name, continent code, ip address and suggested currency. You can use the locale header to get the data in a supported language.\r\n\r\n([IP Geolocation by DB-IP](https:\/\/db-ip.com))",
"responses": {
"200": {
"description": "Locale",
@@ -12312,7 +12312,7 @@
},
"\/locale\/codes": {
"get": {
- "summary": "List Locale Codes",
+ "summary": "List locale codes",
"operationId": "localeListCodes",
"consumes": [
"application\/json"
@@ -12968,7 +12968,7 @@
"tags": [
"messaging"
],
- "description": "Update an email message by its unique ID.\n",
+ "description": "Update an email message by its unique ID.\r\n",
"responses": {
"200": {
"description": "Message",
@@ -13302,7 +13302,7 @@
"tags": [
"messaging"
],
- "description": "Update a push notification by its unique ID.\n",
+ "description": "Update a push notification by its unique ID.\r\n",
"responses": {
"200": {
"description": "Message",
@@ -13596,7 +13596,7 @@
"tags": [
"messaging"
],
- "description": "Update an email message by its unique ID.\n",
+ "description": "Update an email message by its unique ID.\r\n",
"responses": {
"200": {
"description": "Message",
@@ -13715,7 +13715,7 @@
"tags": [
"messaging"
],
- "description": "Get a message by its unique ID.\n",
+ "description": "Get a message by its unique ID.\r\n",
"responses": {
"200": {
"description": "Message",
@@ -16355,7 +16355,7 @@
"tags": [
"messaging"
],
- "description": "Get a provider by its unique ID.\n",
+ "description": "Get a provider by its unique ID.\r\n",
"responses": {
"200": {
"description": "Provider",
@@ -16803,7 +16803,7 @@
"tags": [
"messaging"
],
- "description": "Get a topic by its unique ID.\n",
+ "description": "Get a topic by its unique ID.\r\n",
"responses": {
"200": {
"description": "Topic",
@@ -16866,7 +16866,7 @@
"tags": [
"messaging"
],
- "description": "Update a topic by its unique ID.\n",
+ "description": "Update a topic by its unique ID.\r\n",
"responses": {
"200": {
"description": "Topic",
@@ -17270,7 +17270,7 @@
"tags": [
"messaging"
],
- "description": "Get a subscriber by its unique ID.\n",
+ "description": "Get a subscriber by its unique ID.\r\n",
"responses": {
"200": {
"description": "Subscriber",
@@ -17981,7 +17981,7 @@
"tags": [
"storage"
],
- "description": "Create a new file. Before using this route, you should create a new bucket resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/storage#storageCreateBucket) API or directly from your Appwrite console.\n\nLarger files should be uploaded using multiple requests with the [content-range](https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Content-Range) header to send a partial request with a maximum supported chunk of `5MB`. The `content-range` header values should always be in bytes.\n\nWhen the first request is sent, the server will return the **File** object, and the subsequent part request must include the file's **id** in `x-appwrite-id` header to allow the server to know that the partial upload is for the existing file and not for a new one.\n\nIf you're creating a new file using one of the Appwrite SDKs, all the chunking logic will be managed by the SDK internally.\n",
+ "description": "Create a new file. Before using this route, you should create a new bucket resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/storage#storageCreateBucket) API or directly from your Appwrite console.\r\n\r\nLarger files should be uploaded using multiple requests with the [content-range](https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Content-Range) header to send a partial request with a maximum supported chunk of `5MB`. The `content-range` header values should always be in bytes.\r\n\r\nWhen the first request is sent, the server will return the **File** object, and the subsequent part request must include the file's **id** in `x-appwrite-id` header to allow the server to know that the partial upload is for the existing file and not for a new one.\r\n\r\nIf you're creating a new file using one of the Appwrite SDKs, all the chunking logic will be managed by the SDK internally.\r\n",
"responses": {
"201": {
"description": "File",
@@ -18238,7 +18238,7 @@
]
},
"delete": {
- "summary": "Delete File",
+ "summary": "Delete file",
"operationId": "storageDeleteFile",
"consumes": [
"application\/json"
@@ -18575,7 +18575,8 @@
"jpeg",
"gif",
"png",
- "webp"
+ "webp",
+ "avif"
],
"x-enum-name": "ImageFormat",
"x-enum-keys": [],
@@ -19149,7 +19150,7 @@
"tags": [
"teams"
],
- "description": "Invite a new member to join your team. Provide an ID for existing users, or invite unregistered users using an email or phone number. If initiated from a Client SDK, Appwrite will send an email or sms with a link to join the team to the invited user, and an account will be created for them if one doesn't exist. If initiated from a Server SDK, the new member will be added automatically to the team.\n\nYou only need to provide one of a user ID, email, or phone number. Appwrite will prioritize accepting the user ID > email > phone number if you provide more than one of these parameters.\n\nUse the `url` parameter to redirect the user from the invitation email to your app. After the user is redirected, use the [Update Team Membership Status](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/teams#updateMembershipStatus) endpoint to allow the user to accept the invitation to the team. \n\nPlease note that to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) Appwrite will accept the only redirect URLs under the domains you have added as a platform on the Appwrite Console.\n",
+ "description": "Invite a new member to join your team. Provide an ID for existing users, or invite unregistered users using an email or phone number. If initiated from a Client SDK, Appwrite will send an email or sms with a link to join the team to the invited user, and an account will be created for them if one doesn't exist. If initiated from a Server SDK, the new member will be added automatically to the team.\r\n\r\nYou only need to provide one of a user ID, email, or phone number. Appwrite will prioritize accepting the user ID > email > phone number if you provide more than one of these parameters.\r\n\r\nUse the `url` parameter to redirect the user from the invitation email to your app. After the user is redirected, use the [Update Team Membership Status](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/teams#updateMembershipStatus) endpoint to allow the user to accept the invitation to the team. \r\n\r\nPlease note that to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) Appwrite will accept the only redirect URLs under the domains you have added as a platform on the Appwrite Console.\r\n",
"responses": {
"201": {
"description": "Membership",
@@ -19342,7 +19343,7 @@
"tags": [
"teams"
],
- "description": "Modify the roles of a team member. Only team members with the owner role have access to this endpoint. Learn more about [roles and permissions](https:\/\/appwrite.io\/docs\/permissions).\n",
+ "description": "Modify the roles of a team member. Only team members with the owner role have access to this endpoint. Learn more about [roles and permissions](https:\/\/appwrite.io\/docs\/permissions).\r\n",
"responses": {
"200": {
"description": "Membership",
@@ -19508,7 +19509,7 @@
"tags": [
"teams"
],
- "description": "Use this endpoint to allow a user to accept an invitation to join a team after being redirected back to your app from the invitation email received by the user.\n\nIf the request is successful, a session for the user is automatically created.\n",
+ "description": "Use this endpoint to allow a user to accept an invitation to join a team after being redirected back to your app from the invitation email received by the user.\r\n\r\nIf the request is successful, a session for the user is automatically created.\r\n",
"responses": {
"200": {
"description": "Membership",
@@ -20105,7 +20106,7 @@
},
"\/users\/identities": {
"get": {
- "summary": "List Identities",
+ "summary": "List identities",
"operationId": "usersListIdentities",
"consumes": [
"application\/json"
@@ -21087,7 +21088,7 @@
"tags": [
"users"
],
- "description": "Update the user labels by its unique ID. \n\nLabels can be used to grant access to resources. While teams are a way for user's to share access to a resource, labels can be defined by the developer to grant access without an invitation. See the [Permissions docs](https:\/\/appwrite.io\/docs\/permissions) for more info.",
+ "description": "Update the user labels by its unique ID. \r\n\r\nLabels can be used to grant access to resources. While teams are a way for user's to share access to a resource, labels can be defined by the developer to grant access without an invitation. See the [Permissions docs](https:\/\/appwrite.io\/docs\/permissions) for more info.",
"responses": {
"200": {
"description": "User",
@@ -21383,7 +21384,7 @@
},
"\/users\/{userId}\/mfa\/authenticators\/{type}": {
"delete": {
- "summary": "Delete Authenticator",
+ "summary": "Delete authenticator",
"operationId": "usersDeleteMfaAuthenticator",
"consumes": [
"application\/json"
@@ -21460,7 +21461,7 @@
},
"\/users\/{userId}\/mfa\/factors": {
"get": {
- "summary": "List Factors",
+ "summary": "List factors",
"operationId": "usersListMfaFactors",
"consumes": [
"application\/json"
@@ -21524,7 +21525,7 @@
},
"\/users\/{userId}\/mfa\/recovery-codes": {
"get": {
- "summary": "Get MFA Recovery Codes",
+ "summary": "Get MFA recovery codes",
"operationId": "usersGetMfaRecoveryCodes",
"consumes": [
"application\/json"
@@ -21586,7 +21587,7 @@
]
},
"put": {
- "summary": "Regenerate MFA Recovery Codes",
+ "summary": "Regenerate MFA recovery codes",
"operationId": "usersUpdateMfaRecoveryCodes",
"consumes": [
"application\/json"
@@ -21648,7 +21649,7 @@
]
},
"patch": {
- "summary": "Create MFA Recovery Codes",
+ "summary": "Create MFA recovery codes",
"operationId": "usersCreateMfaRecoveryCodes",
"consumes": [
"application\/json"
@@ -22175,7 +22176,7 @@
"tags": [
"users"
],
- "description": "Creates a session for a user. Returns an immediately usable session object.\n\nIf you want to generate a token for a custom authentication flow, use the [POST \/users\/{userId}\/tokens](https:\/\/appwrite.io\/docs\/server\/users#createToken) endpoint.",
+ "description": "Creates a session for a user. Returns an immediately usable session object.\r\n\r\nIf you want to generate a token for a custom authentication flow, use the [POST \/users\/{userId}\/tokens](https:\/\/appwrite.io\/docs\/server\/users#createToken) endpoint.",
"responses": {
"201": {
"description": "Session",
@@ -22434,7 +22435,7 @@
},
"\/users\/{userId}\/targets": {
"get": {
- "summary": "List User Targets",
+ "summary": "List user targets",
"operationId": "usersListTargets",
"consumes": [
"application\/json"
@@ -22509,7 +22510,7 @@
]
},
"post": {
- "summary": "Create User Target",
+ "summary": "Create user target",
"operationId": "usersCreateTarget",
"consumes": [
"application\/json"
@@ -22625,7 +22626,7 @@
},
"\/users\/{userId}\/targets\/{targetId}": {
"get": {
- "summary": "Get User Target",
+ "summary": "Get user target",
"operationId": "usersGetTarget",
"consumes": [
"application\/json"
@@ -22696,7 +22697,7 @@
]
},
"patch": {
- "summary": "Update User target",
+ "summary": "Update user target",
"operationId": "usersUpdateTarget",
"consumes": [
"application\/json"
@@ -22875,7 +22876,7 @@
"tags": [
"users"
],
- "description": "Returns a token with a secret key for creating a session. Use the user ID and secret and submit a request to the [PUT \/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process.\n",
+ "description": "Returns a token with a secret key for creating a session. Use the user ID and secret and submit a request to the [PUT \/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process.\r\n",
"responses": {
"201": {
"description": "Token",
@@ -24166,6 +24167,16 @@
"x-example": false,
"x-nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"size": {
"type": "integer",
"description": "Attribute size.",
@@ -24185,6 +24196,8 @@
"status",
"error",
"required",
+ "$createdAt",
+ "$updatedAt",
"size"
]
},
@@ -24223,6 +24236,16 @@
"x-example": false,
"x-nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"min": {
"type": "integer",
"description": "Minimum value to enforce for new documents.",
@@ -24250,7 +24273,9 @@
"type",
"status",
"error",
- "required"
+ "required",
+ "$createdAt",
+ "$updatedAt"
]
},
"attributeFloat": {
@@ -24288,6 +24313,16 @@
"x-example": false,
"x-nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"min": {
"type": "number",
"description": "Minimum value to enforce for new documents.",
@@ -24315,7 +24350,9 @@
"type",
"status",
"error",
- "required"
+ "required",
+ "$createdAt",
+ "$updatedAt"
]
},
"attributeBoolean": {
@@ -24353,6 +24390,16 @@
"x-example": false,
"x-nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"default": {
"type": "boolean",
"description": "Default value for attribute when not provided. Cannot be set when attribute is required.",
@@ -24365,7 +24412,9 @@
"type",
"status",
"error",
- "required"
+ "required",
+ "$createdAt",
+ "$updatedAt"
]
},
"attributeEmail": {
@@ -24403,6 +24452,16 @@
"x-example": false,
"x-nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"format": {
"type": "string",
"description": "String format.",
@@ -24421,6 +24480,8 @@
"status",
"error",
"required",
+ "$createdAt",
+ "$updatedAt",
"format"
]
},
@@ -24459,6 +24520,16 @@
"x-example": false,
"x-nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"elements": {
"type": "array",
"description": "Array of elements in enumerated type.",
@@ -24485,6 +24556,8 @@
"status",
"error",
"required",
+ "$createdAt",
+ "$updatedAt",
"elements",
"format"
]
@@ -24524,6 +24597,16 @@
"x-example": false,
"x-nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"format": {
"type": "string",
"description": "String format.",
@@ -24542,6 +24625,8 @@
"status",
"error",
"required",
+ "$createdAt",
+ "$updatedAt",
"format"
]
},
@@ -24580,6 +24665,16 @@
"x-example": false,
"x-nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"format": {
"type": "string",
"description": "String format.",
@@ -24598,6 +24693,8 @@
"status",
"error",
"required",
+ "$createdAt",
+ "$updatedAt",
"format"
]
},
@@ -24636,6 +24733,16 @@
"x-example": false,
"x-nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"format": {
"type": "string",
"description": "ISO 8601 format.",
@@ -24654,6 +24761,8 @@
"status",
"error",
"required",
+ "$createdAt",
+ "$updatedAt",
"format"
]
},
@@ -24692,6 +24801,16 @@
"x-example": false,
"x-nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"relatedCollection": {
"type": "string",
"description": "The ID of the related collection.",
@@ -24729,6 +24848,8 @@
"status",
"error",
"required",
+ "$createdAt",
+ "$updatedAt",
"relatedCollection",
"relationType",
"twoWay",
@@ -24777,6 +24898,16 @@
},
"x-example": [],
"x-nullable": true
+ },
+ "$createdAt": {
+ "type": "string",
+ "description": "Index creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Index update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
}
},
"required": [
@@ -24784,7 +24915,9 @@
"type",
"status",
"error",
- "attributes"
+ "attributes",
+ "$createdAt",
+ "$updatedAt"
]
},
"document": {
@@ -26458,9 +26591,9 @@
"format": "int32"
},
"responseBody": {
- "type": "string",
+ "type": "payload",
"description": "HTTP response body. This will return empty unless execution is created as synchronous.",
- "x-example": "Developers are awesome."
+ "x-example": ""
},
"responseHeaders": {
"type": "array",
diff --git a/app/config/specs/swagger2-latest-client.json b/app/config/specs/swagger2-latest-client.json
index ce9ea857bb..fce6a871a3 100644
--- a/app/config/specs/swagger2-latest-client.json
+++ b/app/config/specs/swagger2-latest-client.json
@@ -222,7 +222,7 @@
"tags": [
"account"
],
- "description": "Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. For security measures, user password is required to complete this request.\nThis endpoint can also be used to convert an anonymous account to a normal one, by passing an email address and a new password.\n",
+ "description": "Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. For security measures, user password is required to complete this request.\r\nThis endpoint can also be used to convert an anonymous account to a normal one, by passing an email address and a new password.\r\n",
"responses": {
"200": {
"description": "User",
@@ -293,7 +293,7 @@
},
"\/account\/identities": {
"get": {
- "summary": "List Identities",
+ "summary": "List identities",
"operationId": "accountListIdentities",
"consumes": [
"application\/json"
@@ -619,7 +619,7 @@
},
"\/account\/mfa\/authenticators\/{type}": {
"post": {
- "summary": "Create Authenticator",
+ "summary": "Create authenticator",
"operationId": "accountCreateMfaAuthenticator",
"consumes": [
"application\/json"
@@ -687,7 +687,7 @@
]
},
"put": {
- "summary": "Verify Authenticator",
+ "summary": "Verify authenticator",
"operationId": "accountUpdateMfaAuthenticator",
"consumes": [
"application\/json"
@@ -773,7 +773,7 @@
]
},
"delete": {
- "summary": "Delete Authenticator",
+ "summary": "Delete authenticator",
"operationId": "accountDeleteMfaAuthenticator",
"consumes": [
"application\/json"
@@ -838,7 +838,7 @@
},
"\/account\/mfa\/challenge": {
"post": {
- "summary": "Create MFA Challenge",
+ "summary": "Create MFA challenge",
"operationId": "accountCreateMfaChallenge",
"consumes": [
"application\/json"
@@ -917,7 +917,7 @@
]
},
"put": {
- "summary": "Create MFA Challenge (confirmation)",
+ "summary": "Create MFA challenge (confirmation)",
"operationId": "accountUpdateMfaChallenge",
"consumes": [
"application\/json"
@@ -994,7 +994,7 @@
},
"\/account\/mfa\/factors": {
"get": {
- "summary": "List Factors",
+ "summary": "List factors",
"operationId": "accountListMfaFactors",
"consumes": [
"application\/json"
@@ -1049,7 +1049,7 @@
},
"\/account\/mfa\/recovery-codes": {
"get": {
- "summary": "Get MFA Recovery Codes",
+ "summary": "Get MFA recovery codes",
"operationId": "accountGetMfaRecoveryCodes",
"consumes": [
"application\/json"
@@ -1102,7 +1102,7 @@
]
},
"post": {
- "summary": "Create MFA Recovery Codes",
+ "summary": "Create MFA recovery codes",
"operationId": "accountCreateMfaRecoveryCodes",
"consumes": [
"application\/json"
@@ -1155,7 +1155,7 @@
]
},
"patch": {
- "summary": "Regenerate MFA Recovery Codes",
+ "summary": "Regenerate MFA recovery codes",
"operationId": "accountUpdateMfaRecoveryCodes",
"consumes": [
"application\/json"
@@ -1670,7 +1670,7 @@
"tags": [
"account"
],
- "description": "Use this endpoint to complete the user account password reset. Both the **userId** and **secret** arguments will be passed as query parameters to the redirect URL you have provided when sending your request to the [POST \/account\/recovery](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createRecovery) endpoint.\n\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.",
+ "description": "Use this endpoint to complete the user account password reset. Both the **userId** and **secret** arguments will be passed as query parameters to the redirect URL you have provided when sending your request to the [POST \/account\/recovery](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createRecovery) endpoint.\r\n\r\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.",
"responses": {
"200": {
"description": "Token",
@@ -1915,7 +1915,7 @@
"tags": [
"account"
],
- "description": "Allow the user to login into their account by providing a valid email and password combination. This route will create a new session for the user.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
+ "description": "Allow the user to login into their account by providing a valid email and password combination. This route will create a new session for the user.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
"responses": {
"201": {
"description": "Session",
@@ -2075,7 +2075,7 @@
"tags": [
"account"
],
- "description": "Allow the user to login to their account using the OAuth2 provider of their choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed.\n\nIf there is already an active session, the new session will be attached to the logged-in account. If there are no active sessions, the server will attempt to look for a user with the same email address as the email received from the OAuth2 provider and attach the new session to the existing user. If no matching user is found - the server will create a new user.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\n",
+ "description": "Allow the user to login to their account using the OAuth2 provider of their choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed.\r\n\r\nIf there is already an active session, the new session will be attached to the logged-in account. If there are no active sessions, the server will attempt to look for a user with the same email address as the email received from the OAuth2 provider and attach the new session to the existing user. If no matching user is found - the server will create a new user.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\r\n",
"responses": {
"301": {
"description": "No content"
@@ -2836,7 +2836,7 @@
"tags": [
"account"
],
- "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's email is valid for 15 minutes.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
+ "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's email is valid for 15 minutes.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
"responses": {
"201": {
"description": "Token",
@@ -2922,7 +2922,7 @@
"tags": [
"account"
],
- "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour. If you are on a mobile device you can leave the URL parameter empty, so that the login completion will be handled by your Appwrite instance by default.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\n",
+ "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour. If you are on a mobile device you can leave the URL parameter empty, so that the login completion will be handled by your Appwrite instance by default.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\r\n",
"responses": {
"201": {
"description": "Token",
@@ -3017,7 +3017,7 @@
"tags": [
"account"
],
- "description": "Allow the user to login to their account using the OAuth2 provider of their choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed. \n\nIf authentication succeeds, `userId` and `secret` of a token will be appended to the success URL as query parameters. These can be used to create a new session using the [Create session](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
+ "description": "Allow the user to login to their account using the OAuth2 provider of their choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed. \r\n\r\nIf authentication succeeds, `userId` and `secret` of a token will be appended to the success URL as query parameters. These can be used to create a new session using the [Create session](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
"responses": {
"301": {
"description": "No content"
@@ -3152,7 +3152,7 @@
"tags": [
"account"
],
- "description": "Sends the user an SMS with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's phone is valid for 15 minutes.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
+ "description": "Sends the user an SMS with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's phone is valid for 15 minutes.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
"responses": {
"201": {
"description": "Token",
@@ -3235,7 +3235,7 @@
"tags": [
"account"
],
- "description": "Use this endpoint to send a verification message to your user email address to confirm they are the valid owners of that address. Both the **userId** and **secret** arguments will be passed as query parameters to the URL you have provided to be attached to the verification email. The provided URL should redirect the user back to your app and allow you to complete the verification process by verifying both the **userId** and **secret** parameters. Learn more about how to [complete the verification process](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\n\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.\n",
+ "description": "Use this endpoint to send a verification message to your user email address to confirm they are the valid owners of that address. Both the **userId** and **secret** arguments will be passed as query parameters to the URL you have provided to be attached to the verification email. The provided URL should redirect the user back to your app and allow you to complete the verification process by verifying both the **userId** and **secret** parameters. Learn more about how to [complete the verification process](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\r\n\r\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.\r\n",
"responses": {
"201": {
"description": "Token",
@@ -3528,7 +3528,7 @@
"tags": [
"avatars"
],
- "description": "You can use this endpoint to show different browser icons to your users. The code argument receives the browser code as it appears in your user [GET \/account\/sessions](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#getSessions) endpoint. Use width, height and quality arguments to change the output settings.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.",
+ "description": "You can use this endpoint to show different browser icons to your users. The code argument receives the browser code as it appears in your user [GET \/account\/sessions](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#getSessions) endpoint. Use width, height and quality arguments to change the output settings.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.",
"responses": {
"200": {
"description": "Image",
@@ -3657,7 +3657,7 @@
"tags": [
"avatars"
],
- "description": "The credit card endpoint will return you the icon of the credit card provider you need. Use width, height and quality arguments to change the output settings.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n",
+ "description": "The credit card endpoint will return you the icon of the credit card provider you need. Use width, height and quality arguments to change the output settings.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n",
"responses": {
"200": {
"description": "Image",
@@ -3790,7 +3790,7 @@
"tags": [
"avatars"
],
- "description": "Use this endpoint to fetch the favorite icon (AKA favicon) of any remote website URL.\n\nThis endpoint does not follow HTTP redirects.",
+ "description": "Use this endpoint to fetch the favorite icon (AKA favicon) of any remote website URL.\r\n\r\nThis endpoint does not follow HTTP redirects.",
"responses": {
"200": {
"description": "Image",
@@ -3857,7 +3857,7 @@
"tags": [
"avatars"
],
- "description": "You can use this endpoint to show different country flags icons to your users. The code argument receives the 2 letter country code. Use width, height and quality arguments to change the output settings. Country codes follow the [ISO 3166-1](https:\/\/en.wikipedia.org\/wiki\/ISO_3166-1) standard.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n",
+ "description": "You can use this endpoint to show different country flags icons to your users. The code argument receives the 2 letter country code. Use width, height and quality arguments to change the output settings. Country codes follow the [ISO 3166-1](https:\/\/en.wikipedia.org\/wiki\/ISO_3166-1) standard.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n",
"responses": {
"200": {
"description": "Image",
@@ -4348,7 +4348,7 @@
"tags": [
"avatars"
],
- "description": "Use this endpoint to fetch a remote image URL and crop it to any image size you want. This endpoint is very useful if you need to crop and display remote images in your app or in case you want to make sure a 3rd party image is properly served using a TLS protocol.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 400x400px.\n\nThis endpoint does not follow HTTP redirects.",
+ "description": "Use this endpoint to fetch a remote image URL and crop it to any image size you want. This endpoint is very useful if you need to crop and display remote images in your app or in case you want to make sure a 3rd party image is properly served using a TLS protocol.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 400x400px.\r\n\r\nThis endpoint does not follow HTTP redirects.",
"responses": {
"200": {
"description": "Image",
@@ -4435,7 +4435,7 @@
"tags": [
"avatars"
],
- "description": "Use this endpoint to show your user initials avatar icon on your website or app. By default, this route will try to print your logged-in user name or email initials. You can also overwrite the user name if you pass the 'name' parameter. If no name is given and no user is logged, an empty avatar will be returned.\n\nYou can use the color and background params to change the avatar colors. By default, a random theme will be selected. The random theme will persist for the user's initials when reloading the same theme will always return for the same initials.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n",
+ "description": "Use this endpoint to show your user initials avatar icon on your website or app. By default, this route will try to print your logged-in user name or email initials. You can also overwrite the user name if you pass the 'name' parameter. If no name is given and no user is logged, an empty avatar will be returned.\r\n\r\nYou can use the color and background params to change the avatar colors. By default, a random theme will be selected. The random theme will persist for the user's initials when reloading the same theme will always return for the same initials.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n",
"responses": {
"200": {
"description": "Image",
@@ -4530,7 +4530,7 @@
"tags": [
"avatars"
],
- "description": "Converts a given plain text to a QR code image. You can use the query parameters to change the size and style of the resulting image.\n",
+ "description": "Converts a given plain text to a QR code image. You can use the query parameters to change the size and style of the resulting image.\r\n",
"responses": {
"200": {
"description": "Image",
@@ -5528,7 +5528,7 @@
"tags": [
"locale"
],
- "description": "Get the current user location based on IP. Returns an object with user country code, country name, continent name, continent code, ip address and suggested currency. You can use the locale header to get the data in a supported language.\n\n([IP Geolocation by DB-IP](https:\/\/db-ip.com))",
+ "description": "Get the current user location based on IP. Returns an object with user country code, country name, continent name, continent code, ip address and suggested currency. You can use the locale header to get the data in a supported language.\r\n\r\n([IP Geolocation by DB-IP](https:\/\/db-ip.com))",
"responses": {
"200": {
"description": "Locale",
@@ -5573,7 +5573,7 @@
},
"\/locale\/codes": {
"get": {
- "summary": "List Locale Codes",
+ "summary": "List locale codes",
"operationId": "localeListCodes",
"consumes": [
"application\/json"
@@ -6225,7 +6225,7 @@
"tags": [
"storage"
],
- "description": "Create a new file. Before using this route, you should create a new bucket resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/storage#storageCreateBucket) API or directly from your Appwrite console.\n\nLarger files should be uploaded using multiple requests with the [content-range](https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Content-Range) header to send a partial request with a maximum supported chunk of `5MB`. The `content-range` header values should always be in bytes.\n\nWhen the first request is sent, the server will return the **File** object, and the subsequent part request must include the file's **id** in `x-appwrite-id` header to allow the server to know that the partial upload is for the existing file and not for a new one.\n\nIf you're creating a new file using one of the Appwrite SDKs, all the chunking logic will be managed by the SDK internally.\n",
+ "description": "Create a new file. Before using this route, you should create a new bucket resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/storage#storageCreateBucket) API or directly from your Appwrite console.\r\n\r\nLarger files should be uploaded using multiple requests with the [content-range](https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Content-Range) header to send a partial request with a maximum supported chunk of `5MB`. The `content-range` header values should always be in bytes.\r\n\r\nWhen the first request is sent, the server will return the **File** object, and the subsequent part request must include the file's **id** in `x-appwrite-id` header to allow the server to know that the partial upload is for the existing file and not for a new one.\r\n\r\nIf you're creating a new file using one of the Appwrite SDKs, all the chunking logic will be managed by the SDK internally.\r\n",
"responses": {
"201": {
"description": "File",
@@ -6476,7 +6476,7 @@
]
},
"delete": {
- "summary": "Delete File",
+ "summary": "Delete file",
"operationId": "storageDeleteFile",
"consumes": [
"application\/json"
@@ -6807,7 +6807,8 @@
"jpeg",
"gif",
"png",
- "webp"
+ "webp",
+ "avif"
],
"x-enum-name": "ImageFormat",
"x-enum-keys": [],
@@ -7367,7 +7368,7 @@
"tags": [
"teams"
],
- "description": "Invite a new member to join your team. Provide an ID for existing users, or invite unregistered users using an email or phone number. If initiated from a Client SDK, Appwrite will send an email or sms with a link to join the team to the invited user, and an account will be created for them if one doesn't exist. If initiated from a Server SDK, the new member will be added automatically to the team.\n\nYou only need to provide one of a user ID, email, or phone number. Appwrite will prioritize accepting the user ID > email > phone number if you provide more than one of these parameters.\n\nUse the `url` parameter to redirect the user from the invitation email to your app. After the user is redirected, use the [Update Team Membership Status](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/teams#updateMembershipStatus) endpoint to allow the user to accept the invitation to the team. \n\nPlease note that to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) Appwrite will accept the only redirect URLs under the domains you have added as a platform on the Appwrite Console.\n",
+ "description": "Invite a new member to join your team. Provide an ID for existing users, or invite unregistered users using an email or phone number. If initiated from a Client SDK, Appwrite will send an email or sms with a link to join the team to the invited user, and an account will be created for them if one doesn't exist. If initiated from a Server SDK, the new member will be added automatically to the team.\r\n\r\nYou only need to provide one of a user ID, email, or phone number. Appwrite will prioritize accepting the user ID > email > phone number if you provide more than one of these parameters.\r\n\r\nUse the `url` parameter to redirect the user from the invitation email to your app. After the user is redirected, use the [Update Team Membership Status](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/teams#updateMembershipStatus) endpoint to allow the user to accept the invitation to the team. \r\n\r\nPlease note that to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) Appwrite will accept the only redirect URLs under the domains you have added as a platform on the Appwrite Console.\r\n",
"responses": {
"201": {
"description": "Membership",
@@ -7556,7 +7557,7 @@
"tags": [
"teams"
],
- "description": "Modify the roles of a team member. Only team members with the owner role have access to this endpoint. Learn more about [roles and permissions](https:\/\/appwrite.io\/docs\/permissions).\n",
+ "description": "Modify the roles of a team member. Only team members with the owner role have access to this endpoint. Learn more about [roles and permissions](https:\/\/appwrite.io\/docs\/permissions).\r\n",
"responses": {
"200": {
"description": "Membership",
@@ -7718,7 +7719,7 @@
"tags": [
"teams"
],
- "description": "Use this endpoint to allow a user to accept an invitation to join a team after being redirected back to your app from the invitation email received by the user.\n\nIf the request is successful, a session for the user is automatically created.\n",
+ "description": "Use this endpoint to allow a user to accept an invitation to join a team after being redirected back to your app from the invitation email received by the user.\r\n\r\nIf the request is successful, a session for the user is automatically created.\r\n",
"responses": {
"200": {
"description": "Membership",
@@ -9589,9 +9590,9 @@
"format": "int32"
},
"responseBody": {
- "type": "string",
+ "type": "payload",
"description": "HTTP response body. This will return empty unless execution is created as synchronous.",
- "x-example": "Developers are awesome."
+ "x-example": ""
},
"responseHeaders": {
"type": "array",
diff --git a/app/config/specs/swagger2-latest-console.json b/app/config/specs/swagger2-latest-console.json
index 51935a5e01..9200c80593 100644
--- a/app/config/specs/swagger2-latest-console.json
+++ b/app/config/specs/swagger2-latest-console.json
@@ -278,7 +278,7 @@
"tags": [
"account"
],
- "description": "Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. For security measures, user password is required to complete this request.\nThis endpoint can also be used to convert an anonymous account to a normal one, by passing an email address and a new password.\n",
+ "description": "Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. For security measures, user password is required to complete this request.\r\nThis endpoint can also be used to convert an anonymous account to a normal one, by passing an email address and a new password.\r\n",
"responses": {
"200": {
"description": "User",
@@ -348,7 +348,7 @@
},
"\/account\/identities": {
"get": {
- "summary": "List Identities",
+ "summary": "List identities",
"operationId": "accountListIdentities",
"consumes": [
"application\/json"
@@ -670,7 +670,7 @@
},
"\/account\/mfa\/authenticators\/{type}": {
"post": {
- "summary": "Create Authenticator",
+ "summary": "Create authenticator",
"operationId": "accountCreateMfaAuthenticator",
"consumes": [
"application\/json"
@@ -737,7 +737,7 @@
]
},
"put": {
- "summary": "Verify Authenticator",
+ "summary": "Verify authenticator",
"operationId": "accountUpdateMfaAuthenticator",
"consumes": [
"application\/json"
@@ -822,7 +822,7 @@
]
},
"delete": {
- "summary": "Delete Authenticator",
+ "summary": "Delete authenticator",
"operationId": "accountDeleteMfaAuthenticator",
"consumes": [
"application\/json"
@@ -886,7 +886,7 @@
},
"\/account\/mfa\/challenge": {
"post": {
- "summary": "Create MFA Challenge",
+ "summary": "Create MFA challenge",
"operationId": "accountCreateMfaChallenge",
"consumes": [
"application\/json"
@@ -965,7 +965,7 @@
]
},
"put": {
- "summary": "Create MFA Challenge (confirmation)",
+ "summary": "Create MFA challenge (confirmation)",
"operationId": "accountUpdateMfaChallenge",
"consumes": [
"application\/json"
@@ -1041,7 +1041,7 @@
},
"\/account\/mfa\/factors": {
"get": {
- "summary": "List Factors",
+ "summary": "List factors",
"operationId": "accountListMfaFactors",
"consumes": [
"application\/json"
@@ -1095,7 +1095,7 @@
},
"\/account\/mfa\/recovery-codes": {
"get": {
- "summary": "Get MFA Recovery Codes",
+ "summary": "Get MFA recovery codes",
"operationId": "accountGetMfaRecoveryCodes",
"consumes": [
"application\/json"
@@ -1147,7 +1147,7 @@
]
},
"post": {
- "summary": "Create MFA Recovery Codes",
+ "summary": "Create MFA recovery codes",
"operationId": "accountCreateMfaRecoveryCodes",
"consumes": [
"application\/json"
@@ -1199,7 +1199,7 @@
]
},
"patch": {
- "summary": "Regenerate MFA Recovery Codes",
+ "summary": "Regenerate MFA recovery codes",
"operationId": "accountUpdateMfaRecoveryCodes",
"consumes": [
"application\/json"
@@ -1707,7 +1707,7 @@
"tags": [
"account"
],
- "description": "Use this endpoint to complete the user account password reset. Both the **userId** and **secret** arguments will be passed as query parameters to the redirect URL you have provided when sending your request to the [POST \/account\/recovery](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createRecovery) endpoint.\n\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.",
+ "description": "Use this endpoint to complete the user account password reset. Both the **userId** and **secret** arguments will be passed as query parameters to the redirect URL you have provided when sending your request to the [POST \/account\/recovery](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createRecovery) endpoint.\r\n\r\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.",
"responses": {
"200": {
"description": "Token",
@@ -1949,7 +1949,7 @@
"tags": [
"account"
],
- "description": "Allow the user to login into their account by providing a valid email and password combination. This route will create a new session for the user.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
+ "description": "Allow the user to login into their account by providing a valid email and password combination. This route will create a new session for the user.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
"responses": {
"201": {
"description": "Session",
@@ -2109,7 +2109,7 @@
"tags": [
"account"
],
- "description": "Allow the user to login to their account using the OAuth2 provider of their choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed.\n\nIf there is already an active session, the new session will be attached to the logged-in account. If there are no active sessions, the server will attempt to look for a user with the same email address as the email received from the OAuth2 provider and attach the new session to the existing user. If no matching user is found - the server will create a new user.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\n",
+ "description": "Allow the user to login to their account using the OAuth2 provider of their choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed.\r\n\r\nIf there is already an active session, the new session will be attached to the logged-in account. If there are no active sessions, the server will attempt to look for a user with the same email address as the email received from the OAuth2 provider and attach the new session to the existing user. If no matching user is found - the server will create a new user.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\r\n",
"responses": {
"301": {
"description": "No content"
@@ -2863,7 +2863,7 @@
"tags": [
"account"
],
- "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's email is valid for 15 minutes.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
+ "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's email is valid for 15 minutes.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
"responses": {
"201": {
"description": "Token",
@@ -2949,7 +2949,7 @@
"tags": [
"account"
],
- "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour. If you are on a mobile device you can leave the URL parameter empty, so that the login completion will be handled by your Appwrite instance by default.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\n",
+ "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour. If you are on a mobile device you can leave the URL parameter empty, so that the login completion will be handled by your Appwrite instance by default.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\r\n",
"responses": {
"201": {
"description": "Token",
@@ -3044,7 +3044,7 @@
"tags": [
"account"
],
- "description": "Allow the user to login to their account using the OAuth2 provider of their choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed. \n\nIf authentication succeeds, `userId` and `secret` of a token will be appended to the success URL as query parameters. These can be used to create a new session using the [Create session](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
+ "description": "Allow the user to login to their account using the OAuth2 provider of their choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed. \r\n\r\nIf authentication succeeds, `userId` and `secret` of a token will be appended to the success URL as query parameters. These can be used to create a new session using the [Create session](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
"responses": {
"301": {
"description": "No content"
@@ -3179,7 +3179,7 @@
"tags": [
"account"
],
- "description": "Sends the user an SMS with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's phone is valid for 15 minutes.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
+ "description": "Sends the user an SMS with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's phone is valid for 15 minutes.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
"responses": {
"201": {
"description": "Token",
@@ -3262,7 +3262,7 @@
"tags": [
"account"
],
- "description": "Use this endpoint to send a verification message to your user email address to confirm they are the valid owners of that address. Both the **userId** and **secret** arguments will be passed as query parameters to the URL you have provided to be attached to the verification email. The provided URL should redirect the user back to your app and allow you to complete the verification process by verifying both the **userId** and **secret** parameters. Learn more about how to [complete the verification process](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\n\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.\n",
+ "description": "Use this endpoint to send a verification message to your user email address to confirm they are the valid owners of that address. Both the **userId** and **secret** arguments will be passed as query parameters to the URL you have provided to be attached to the verification email. The provided URL should redirect the user back to your app and allow you to complete the verification process by verifying both the **userId** and **secret** parameters. Learn more about how to [complete the verification process](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\r\n\r\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.\r\n",
"responses": {
"201": {
"description": "Token",
@@ -3551,7 +3551,7 @@
"tags": [
"avatars"
],
- "description": "You can use this endpoint to show different browser icons to your users. The code argument receives the browser code as it appears in your user [GET \/account\/sessions](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#getSessions) endpoint. Use width, height and quality arguments to change the output settings.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.",
+ "description": "You can use this endpoint to show different browser icons to your users. The code argument receives the browser code as it appears in your user [GET \/account\/sessions](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#getSessions) endpoint. Use width, height and quality arguments to change the output settings.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.",
"responses": {
"200": {
"description": "Image",
@@ -3680,7 +3680,7 @@
"tags": [
"avatars"
],
- "description": "The credit card endpoint will return you the icon of the credit card provider you need. Use width, height and quality arguments to change the output settings.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n",
+ "description": "The credit card endpoint will return you the icon of the credit card provider you need. Use width, height and quality arguments to change the output settings.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n",
"responses": {
"200": {
"description": "Image",
@@ -3813,7 +3813,7 @@
"tags": [
"avatars"
],
- "description": "Use this endpoint to fetch the favorite icon (AKA favicon) of any remote website URL.\n\nThis endpoint does not follow HTTP redirects.",
+ "description": "Use this endpoint to fetch the favorite icon (AKA favicon) of any remote website URL.\r\n\r\nThis endpoint does not follow HTTP redirects.",
"responses": {
"200": {
"description": "Image",
@@ -3880,7 +3880,7 @@
"tags": [
"avatars"
],
- "description": "You can use this endpoint to show different country flags icons to your users. The code argument receives the 2 letter country code. Use width, height and quality arguments to change the output settings. Country codes follow the [ISO 3166-1](https:\/\/en.wikipedia.org\/wiki\/ISO_3166-1) standard.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n",
+ "description": "You can use this endpoint to show different country flags icons to your users. The code argument receives the 2 letter country code. Use width, height and quality arguments to change the output settings. Country codes follow the [ISO 3166-1](https:\/\/en.wikipedia.org\/wiki\/ISO_3166-1) standard.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n",
"responses": {
"200": {
"description": "Image",
@@ -4371,7 +4371,7 @@
"tags": [
"avatars"
],
- "description": "Use this endpoint to fetch a remote image URL and crop it to any image size you want. This endpoint is very useful if you need to crop and display remote images in your app or in case you want to make sure a 3rd party image is properly served using a TLS protocol.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 400x400px.\n\nThis endpoint does not follow HTTP redirects.",
+ "description": "Use this endpoint to fetch a remote image URL and crop it to any image size you want. This endpoint is very useful if you need to crop and display remote images in your app or in case you want to make sure a 3rd party image is properly served using a TLS protocol.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 400x400px.\r\n\r\nThis endpoint does not follow HTTP redirects.",
"responses": {
"200": {
"description": "Image",
@@ -4458,7 +4458,7 @@
"tags": [
"avatars"
],
- "description": "Use this endpoint to show your user initials avatar icon on your website or app. By default, this route will try to print your logged-in user name or email initials. You can also overwrite the user name if you pass the 'name' parameter. If no name is given and no user is logged, an empty avatar will be returned.\n\nYou can use the color and background params to change the avatar colors. By default, a random theme will be selected. The random theme will persist for the user's initials when reloading the same theme will always return for the same initials.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n",
+ "description": "Use this endpoint to show your user initials avatar icon on your website or app. By default, this route will try to print your logged-in user name or email initials. You can also overwrite the user name if you pass the 'name' parameter. If no name is given and no user is logged, an empty avatar will be returned.\r\n\r\nYou can use the color and background params to change the avatar colors. By default, a random theme will be selected. The random theme will persist for the user's initials when reloading the same theme will always return for the same initials.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n",
"responses": {
"200": {
"description": "Image",
@@ -4553,7 +4553,7 @@
"tags": [
"avatars"
],
- "description": "Converts a given plain text to a QR code image. You can use the query parameters to change the size and style of the resulting image.\n",
+ "description": "Converts a given plain text to a QR code image. You can use the query parameters to change the size and style of the resulting image.\r\n",
"responses": {
"200": {
"description": "Image",
@@ -4637,7 +4637,7 @@
},
"\/console\/assistant": {
"post": {
- "summary": "Ask Query",
+ "summary": "Ask query",
"operationId": "assistantChat",
"consumes": [
"application\/json"
@@ -4846,7 +4846,7 @@
"tags": [
"databases"
],
- "description": "Create a new Database.\n",
+ "description": "Create a new Database.\r\n",
"responses": {
"201": {
"description": "Database",
@@ -5727,7 +5727,7 @@
"tags": [
"databases"
],
- "description": "Create a boolean attribute.\n",
+ "description": "Create a boolean attribute.\r\n",
"responses": {
"202": {
"description": "AttributeBoolean",
@@ -6159,7 +6159,7 @@
"tags": [
"databases"
],
- "description": "Create an email attribute.\n",
+ "description": "Create an email attribute.\r\n",
"responses": {
"202": {
"description": "AttributeEmail",
@@ -6265,7 +6265,7 @@
"tags": [
"databases"
],
- "description": "Update an email attribute. Changing the `default` value will not update already existing documents.\n",
+ "description": "Update an email attribute. Changing the `default` value will not update already existing documents.\r\n",
"responses": {
"200": {
"description": "AttributeEmail",
@@ -6375,7 +6375,7 @@
"tags": [
"databases"
],
- "description": "Create an enumeration attribute. The `elements` param acts as a white-list of accepted values for this attribute. \n",
+ "description": "Create an enumeration attribute. The `elements` param acts as a white-list of accepted values for this attribute. \r\n",
"responses": {
"202": {
"description": "AttributeEnum",
@@ -6491,7 +6491,7 @@
"tags": [
"databases"
],
- "description": "Update an enum attribute. Changing the `default` value will not update already existing documents.\n",
+ "description": "Update an enum attribute. Changing the `default` value will not update already existing documents.\r\n",
"responses": {
"200": {
"description": "AttributeEnum",
@@ -6611,7 +6611,7 @@
"tags": [
"databases"
],
- "description": "Create a float attribute. Optionally, minimum and maximum values can be provided.\n",
+ "description": "Create a float attribute. Optionally, minimum and maximum values can be provided.\r\n",
"responses": {
"202": {
"description": "AttributeFloat",
@@ -6729,7 +6729,7 @@
"tags": [
"databases"
],
- "description": "Update a float attribute. Changing the `default` value will not update already existing documents.\n",
+ "description": "Update a float attribute. Changing the `default` value will not update already existing documents.\r\n",
"responses": {
"200": {
"description": "AttributeFloat",
@@ -6853,7 +6853,7 @@
"tags": [
"databases"
],
- "description": "Create an integer attribute. Optionally, minimum and maximum values can be provided.\n",
+ "description": "Create an integer attribute. Optionally, minimum and maximum values can be provided.\r\n",
"responses": {
"202": {
"description": "AttributeInteger",
@@ -6971,7 +6971,7 @@
"tags": [
"databases"
],
- "description": "Update an integer attribute. Changing the `default` value will not update already existing documents.\n",
+ "description": "Update an integer attribute. Changing the `default` value will not update already existing documents.\r\n",
"responses": {
"200": {
"description": "AttributeInteger",
@@ -7095,7 +7095,7 @@
"tags": [
"databases"
],
- "description": "Create IP address attribute.\n",
+ "description": "Create IP address attribute.\r\n",
"responses": {
"202": {
"description": "AttributeIP",
@@ -7201,7 +7201,7 @@
"tags": [
"databases"
],
- "description": "Update an ip attribute. Changing the `default` value will not update already existing documents.\n",
+ "description": "Update an ip attribute. Changing the `default` value will not update already existing documents.\r\n",
"responses": {
"200": {
"description": "AttributeIP",
@@ -7311,7 +7311,7 @@
"tags": [
"databases"
],
- "description": "Create relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\n",
+ "description": "Create relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\r\n",
"responses": {
"202": {
"description": "AttributeRelationship",
@@ -7446,7 +7446,7 @@
"tags": [
"databases"
],
- "description": "Create a string attribute.\n",
+ "description": "Create a string attribute.\r\n",
"responses": {
"202": {
"description": "AttributeString",
@@ -7565,7 +7565,7 @@
"tags": [
"databases"
],
- "description": "Update a string attribute. Changing the `default` value will not update already existing documents.\n",
+ "description": "Update a string attribute. Changing the `default` value will not update already existing documents.\r\n",
"responses": {
"200": {
"description": "AttributeString",
@@ -7681,7 +7681,7 @@
"tags": [
"databases"
],
- "description": "Create a URL attribute.\n",
+ "description": "Create a URL attribute.\r\n",
"responses": {
"202": {
"description": "AttributeURL",
@@ -7787,7 +7787,7 @@
"tags": [
"databases"
],
- "description": "Update an url attribute. Changing the `default` value will not update already existing documents.\n",
+ "description": "Update an url attribute. Changing the `default` value will not update already existing documents.\r\n",
"responses": {
"200": {
"description": "AttributeURL",
@@ -8075,7 +8075,7 @@
"tags": [
"databases"
],
- "description": "Update relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\n",
+ "description": "Update relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\r\n",
"responses": {
"200": {
"description": "AttributeRelationship",
@@ -8817,7 +8817,7 @@
"tags": [
"databases"
],
- "description": "Creates an index on the attributes listed. Your index should include all the attributes you will query in a single request.\nAttributes can be `key`, `fulltext`, and `unique`.",
+ "description": "Creates an index on the attributes listed. Your index should include all the attributes you will query in a single request.\r\nAttributes can be `key`, `fulltext`, and `unique`.",
"responses": {
"202": {
"description": "Index",
@@ -9806,7 +9806,7 @@
"tags": [
"functions"
],
- "description": "List allowed function specifications for this instance.\n",
+ "description": "List allowed function specifications for this instance.\r\n",
"responses": {
"200": {
"description": "Specifications List",
@@ -10533,7 +10533,7 @@
"tags": [
"functions"
],
- "description": "Create a new function code deployment. Use this endpoint to upload a new version of your code function. To execute your newly uploaded code, you'll need to update the function's deployment to use your new deployment UID.\n\nThis endpoint accepts a tar.gz file compressed with your code. Make sure to include any dependencies your code has within the compressed file. You can learn more about code packaging in the [Appwrite Cloud Functions tutorial](https:\/\/appwrite.io\/docs\/functions).\n\nUse the \"command\" param to set the entrypoint used to execute your code.",
+ "description": "Create a new function code deployment. Use this endpoint to upload a new version of your code function. To execute your newly uploaded code, you'll need to update the function's deployment to use your new deployment UID.\r\n\r\nThis endpoint accepts a tar.gz file compressed with your code. Make sure to include any dependencies your code has within the compressed file. You can learn more about code packaging in the [Appwrite Cloud Functions tutorial](https:\/\/appwrite.io\/docs\/functions).\r\n\r\nUse the \"command\" param to set the entrypoint used to execute your code.",
"responses": {
"202": {
"description": "Deployment",
@@ -11336,7 +11336,7 @@
"tags": [
"functions"
],
- "description": "Delete a function execution by its unique ID.\n",
+ "description": "Delete a function execution by its unique ID.\r\n",
"responses": {
"204": {
"description": "No content"
@@ -12660,7 +12660,7 @@
"tags": [
"health"
],
- "description": "Returns the amount of failed jobs in a given queue.\n",
+ "description": "Returns the amount of failed jobs in a given queue.\r\n",
"responses": {
"200": {
"description": "Health Queue",
@@ -13419,7 +13419,7 @@
"tags": [
"locale"
],
- "description": "Get the current user location based on IP. Returns an object with user country code, country name, continent name, continent code, ip address and suggested currency. You can use the locale header to get the data in a supported language.\n\n([IP Geolocation by DB-IP](https:\/\/db-ip.com))",
+ "description": "Get the current user location based on IP. Returns an object with user country code, country name, continent name, continent code, ip address and suggested currency. You can use the locale header to get the data in a supported language.\r\n\r\n([IP Geolocation by DB-IP](https:\/\/db-ip.com))",
"responses": {
"200": {
"description": "Locale",
@@ -13464,7 +13464,7 @@
},
"\/locale\/codes": {
"get": {
- "summary": "List Locale Codes",
+ "summary": "List locale codes",
"operationId": "localeListCodes",
"consumes": [
"application\/json"
@@ -14104,7 +14104,7 @@
"tags": [
"messaging"
],
- "description": "Update an email message by its unique ID.\n",
+ "description": "Update an email message by its unique ID.\r\n",
"responses": {
"200": {
"description": "Message",
@@ -14436,7 +14436,7 @@
"tags": [
"messaging"
],
- "description": "Update a push notification by its unique ID.\n",
+ "description": "Update a push notification by its unique ID.\r\n",
"responses": {
"200": {
"description": "Message",
@@ -14728,7 +14728,7 @@
"tags": [
"messaging"
],
- "description": "Update an email message by its unique ID.\n",
+ "description": "Update an email message by its unique ID.\r\n",
"responses": {
"200": {
"description": "Message",
@@ -14846,7 +14846,7 @@
"tags": [
"messaging"
],
- "description": "Get a message by its unique ID.\n",
+ "description": "Get a message by its unique ID.\r\n",
"responses": {
"200": {
"description": "Message",
@@ -17461,7 +17461,7 @@
"tags": [
"messaging"
],
- "description": "Get a provider by its unique ID.\n",
+ "description": "Get a provider by its unique ID.\r\n",
"responses": {
"200": {
"description": "Provider",
@@ -17903,7 +17903,7 @@
"tags": [
"messaging"
],
- "description": "Get a topic by its unique ID.\n",
+ "description": "Get a topic by its unique ID.\r\n",
"responses": {
"200": {
"description": "Topic",
@@ -17965,7 +17965,7 @@
"tags": [
"messaging"
],
- "description": "Update a topic by its unique ID.\n",
+ "description": "Update a topic by its unique ID.\r\n",
"responses": {
"200": {
"description": "Topic",
@@ -18363,7 +18363,7 @@
"tags": [
"messaging"
],
- "description": "Get a subscriber by its unique ID.\n",
+ "description": "Get a subscriber by its unique ID.\r\n",
"responses": {
"200": {
"description": "Subscriber",
@@ -18494,7 +18494,7 @@
},
"\/migrations": {
"get": {
- "summary": "List Migrations",
+ "summary": "List migrations",
"operationId": "migrationsList",
"consumes": [
"application\/json"
@@ -18569,7 +18569,7 @@
},
"\/migrations\/appwrite": {
"post": {
- "summary": "Migrate Appwrite Data",
+ "summary": "Migrate Appwrite data",
"operationId": "migrationsCreateAppwriteMigration",
"consumes": [
"application\/json"
@@ -18665,7 +18665,7 @@
},
"\/migrations\/appwrite\/report": {
"get": {
- "summary": "Generate a report on Appwrite Data",
+ "summary": "Generate a report on Appwrite data",
"operationId": "migrationsGetAppwriteReport",
"consumes": [
"application\/json"
@@ -18755,7 +18755,7 @@
},
"\/migrations\/firebase": {
"post": {
- "summary": "Migrate Firebase Data (Service Account)",
+ "summary": "Migrate Firebase data (Service Account)",
"operationId": "migrationsCreateFirebaseMigration",
"consumes": [
"application\/json"
@@ -18837,7 +18837,7 @@
},
"\/migrations\/firebase\/deauthorize": {
"get": {
- "summary": "Revoke Appwrite's authorization to access Firebase Projects",
+ "summary": "Revoke Appwrite's authorization to access Firebase projects",
"operationId": "migrationsDeleteFirebaseAuth",
"consumes": [
"application\/json"
@@ -18889,7 +18889,7 @@
},
"\/migrations\/firebase\/oauth": {
"post": {
- "summary": "Migrate Firebase Data (OAuth)",
+ "summary": "Migrate Firebase data (OAuth)",
"operationId": "migrationsCreateFirebaseOAuthMigration",
"consumes": [
"application\/json"
@@ -18971,7 +18971,7 @@
},
"\/migrations\/firebase\/projects": {
"get": {
- "summary": "List Firebase Projects",
+ "summary": "List Firebase projects",
"operationId": "migrationsListFirebaseProjects",
"consumes": [
"application\/json"
@@ -19023,7 +19023,7 @@
},
"\/migrations\/firebase\/report": {
"get": {
- "summary": "Generate a report on Firebase Data",
+ "summary": "Generate a report on Firebase data",
"operationId": "migrationsGetFirebaseReport",
"consumes": [
"application\/json"
@@ -19096,7 +19096,7 @@
},
"\/migrations\/firebase\/report\/oauth": {
"get": {
- "summary": "Generate a report on Firebase Data using OAuth",
+ "summary": "Generate a report on Firebase data using OAuth",
"operationId": "migrationsGetFirebaseReportOAuth",
"consumes": [
"application\/json"
@@ -19169,7 +19169,7 @@
},
"\/migrations\/nhost": {
"post": {
- "summary": "Migrate NHost Data",
+ "summary": "Migrate NHost data",
"operationId": "migrationsCreateNHostMigration",
"consumes": [
"application\/json"
@@ -19414,7 +19414,7 @@
},
"\/migrations\/supabase": {
"post": {
- "summary": "Migrate Supabase Data",
+ "summary": "Migrate Supabase data",
"operationId": "migrationsCreateSupabaseMigration",
"consumes": [
"application\/json"
@@ -19645,7 +19645,7 @@
},
"\/migrations\/{migrationId}": {
"get": {
- "summary": "Get Migration",
+ "summary": "Get migration",
"operationId": "migrationsGet",
"consumes": [
"application\/json"
@@ -19705,7 +19705,7 @@
]
},
"patch": {
- "summary": "Retry Migration",
+ "summary": "Retry migration",
"operationId": "migrationsRetry",
"consumes": [
"application\/json"
@@ -19765,7 +19765,7 @@
]
},
"delete": {
- "summary": "Delete Migration",
+ "summary": "Delete migration",
"operationId": "migrationsDelete",
"consumes": [
"application\/json"
@@ -19908,7 +19908,7 @@
},
"\/project\/variables": {
"get": {
- "summary": "List Variables",
+ "summary": "List variables",
"operationId": "projectListVariables",
"consumes": [
"application\/json"
@@ -19958,7 +19958,7 @@
]
},
"post": {
- "summary": "Create Variable",
+ "summary": "Create variable",
"operationId": "projectCreateVariable",
"consumes": [
"application\/json"
@@ -20037,7 +20037,7 @@
},
"\/project\/variables\/{variableId}": {
"get": {
- "summary": "Get Variable",
+ "summary": "Get variable",
"operationId": "projectGetVariable",
"consumes": [
"application\/json"
@@ -20097,7 +20097,7 @@
]
},
"put": {
- "summary": "Update Variable",
+ "summary": "Update variable",
"operationId": "projectUpdateVariable",
"consumes": [
"application\/json"
@@ -20181,7 +20181,7 @@
]
},
"delete": {
- "summary": "Delete Variable",
+ "summary": "Delete variable",
"operationId": "projectDeleteVariable",
"consumes": [
"application\/json"
@@ -21748,7 +21748,7 @@
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
- "scope": "projects.read",
+ "scope": "keys.read",
"platforms": [
"console"
],
@@ -21808,7 +21808,7 @@
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
- "scope": "projects.write",
+ "scope": "keys.write",
"platforms": [
"console"
],
@@ -21904,7 +21904,7 @@
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
- "scope": "projects.read",
+ "scope": "keys.read",
"platforms": [
"console"
],
@@ -21972,7 +21972,7 @@
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
- "scope": "projects.write",
+ "scope": "keys.write",
"platforms": [
"console"
],
@@ -22069,7 +22069,7 @@
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
- "scope": "projects.write",
+ "scope": "keys.write",
"platforms": [
"console"
],
@@ -22280,7 +22280,7 @@
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
- "scope": "projects.read",
+ "scope": "platforms.read",
"platforms": [
"console"
],
@@ -22340,7 +22340,7 @@
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
- "scope": "projects.write",
+ "scope": "platforms.write",
"platforms": [
"console"
],
@@ -22464,7 +22464,7 @@
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
- "scope": "projects.read",
+ "scope": "platforms.read",
"platforms": [
"console"
],
@@ -22532,7 +22532,7 @@
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
- "scope": "projects.write",
+ "scope": "platforms.write",
"platforms": [
"console"
],
@@ -22631,7 +22631,7 @@
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
- "scope": "projects.write",
+ "scope": "platforms.write",
"platforms": [
"console"
],
@@ -23101,7 +23101,8 @@
"default": "",
"x-example": "tls",
"enum": [
- "tls"
+ "tls",
+ "ssl"
],
"x-enum-name": "SMTPSecure",
"x-enum-keys": []
@@ -25101,7 +25102,7 @@
},
"\/proxy\/rules": {
"get": {
- "summary": "List Rules",
+ "summary": "List rules",
"operationId": "proxyListRules",
"consumes": [
"application\/json"
@@ -25174,7 +25175,7 @@
]
},
"post": {
- "summary": "Create Rule",
+ "summary": "Create rule",
"operationId": "proxyCreateRule",
"consumes": [
"application\/json"
@@ -25265,7 +25266,7 @@
},
"\/proxy\/rules\/{ruleId}": {
"get": {
- "summary": "Get Rule",
+ "summary": "Get rule",
"operationId": "proxyGetRule",
"consumes": [
"application\/json"
@@ -25325,7 +25326,7 @@
]
},
"delete": {
- "summary": "Delete Rule",
+ "summary": "Delete rule",
"operationId": "proxyDeleteRule",
"consumes": [
"application\/json"
@@ -25382,7 +25383,7 @@
},
"\/proxy\/rules\/{ruleId}\/verification": {
"patch": {
- "summary": "Update Rule Verification Status",
+ "summary": "Update rule verification status",
"operationId": "proxyUpdateRuleVerification",
"consumes": [
"application\/json"
@@ -26014,7 +26015,7 @@
"tags": [
"storage"
],
- "description": "Create a new file. Before using this route, you should create a new bucket resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/storage#storageCreateBucket) API or directly from your Appwrite console.\n\nLarger files should be uploaded using multiple requests with the [content-range](https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Content-Range) header to send a partial request with a maximum supported chunk of `5MB`. The `content-range` header values should always be in bytes.\n\nWhen the first request is sent, the server will return the **File** object, and the subsequent part request must include the file's **id** in `x-appwrite-id` header to allow the server to know that the partial upload is for the existing file and not for a new one.\n\nIf you're creating a new file using one of the Appwrite SDKs, all the chunking logic will be managed by the SDK internally.\n",
+ "description": "Create a new file. Before using this route, you should create a new bucket resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/storage#storageCreateBucket) API or directly from your Appwrite console.\r\n\r\nLarger files should be uploaded using multiple requests with the [content-range](https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Content-Range) header to send a partial request with a maximum supported chunk of `5MB`. The `content-range` header values should always be in bytes.\r\n\r\nWhen the first request is sent, the server will return the **File** object, and the subsequent part request must include the file's **id** in `x-appwrite-id` header to allow the server to know that the partial upload is for the existing file and not for a new one.\r\n\r\nIf you're creating a new file using one of the Appwrite SDKs, all the chunking logic will be managed by the SDK internally.\r\n",
"responses": {
"201": {
"description": "File",
@@ -26265,7 +26266,7 @@
]
},
"delete": {
- "summary": "Delete File",
+ "summary": "Delete file",
"operationId": "storageDeleteFile",
"consumes": [
"application\/json"
@@ -26596,7 +26597,8 @@
"jpeg",
"gif",
"png",
- "webp"
+ "webp",
+ "avif"
],
"x-enum-name": "ImageFormat",
"x-enum-keys": [],
@@ -27386,7 +27388,7 @@
"tags": [
"teams"
],
- "description": "Invite a new member to join your team. Provide an ID for existing users, or invite unregistered users using an email or phone number. If initiated from a Client SDK, Appwrite will send an email or sms with a link to join the team to the invited user, and an account will be created for them if one doesn't exist. If initiated from a Server SDK, the new member will be added automatically to the team.\n\nYou only need to provide one of a user ID, email, or phone number. Appwrite will prioritize accepting the user ID > email > phone number if you provide more than one of these parameters.\n\nUse the `url` parameter to redirect the user from the invitation email to your app. After the user is redirected, use the [Update Team Membership Status](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/teams#updateMembershipStatus) endpoint to allow the user to accept the invitation to the team. \n\nPlease note that to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) Appwrite will accept the only redirect URLs under the domains you have added as a platform on the Appwrite Console.\n",
+ "description": "Invite a new member to join your team. Provide an ID for existing users, or invite unregistered users using an email or phone number. If initiated from a Client SDK, Appwrite will send an email or sms with a link to join the team to the invited user, and an account will be created for them if one doesn't exist. If initiated from a Server SDK, the new member will be added automatically to the team.\r\n\r\nYou only need to provide one of a user ID, email, or phone number. Appwrite will prioritize accepting the user ID > email > phone number if you provide more than one of these parameters.\r\n\r\nUse the `url` parameter to redirect the user from the invitation email to your app. After the user is redirected, use the [Update Team Membership Status](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/teams#updateMembershipStatus) endpoint to allow the user to accept the invitation to the team. \r\n\r\nPlease note that to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) Appwrite will accept the only redirect URLs under the domains you have added as a platform on the Appwrite Console.\r\n",
"responses": {
"201": {
"description": "Membership",
@@ -27575,7 +27577,7 @@
"tags": [
"teams"
],
- "description": "Modify the roles of a team member. Only team members with the owner role have access to this endpoint. Learn more about [roles and permissions](https:\/\/appwrite.io\/docs\/permissions).\n",
+ "description": "Modify the roles of a team member. Only team members with the owner role have access to this endpoint. Learn more about [roles and permissions](https:\/\/appwrite.io\/docs\/permissions).\r\n",
"responses": {
"200": {
"description": "Membership",
@@ -27737,7 +27739,7 @@
"tags": [
"teams"
],
- "description": "Use this endpoint to allow a user to accept an invitation to join a team after being redirected back to your app from the invitation email received by the user.\n\nIf the request is successful, a session for the user is automatically created.\n",
+ "description": "Use this endpoint to allow a user to accept an invitation to join a team after being redirected back to your app from the invitation email received by the user.\r\n\r\nIf the request is successful, a session for the user is automatically created.\r\n",
"responses": {
"200": {
"description": "Membership",
@@ -28324,7 +28326,7 @@
},
"\/users\/identities": {
"get": {
- "summary": "List Identities",
+ "summary": "List identities",
"operationId": "usersListIdentities",
"consumes": [
"application\/json"
@@ -29369,7 +29371,7 @@
"tags": [
"users"
],
- "description": "Update the user labels by its unique ID. \n\nLabels can be used to grant access to resources. While teams are a way for user's to share access to a resource, labels can be defined by the developer to grant access without an invitation. See the [Permissions docs](https:\/\/appwrite.io\/docs\/permissions) for more info.",
+ "description": "Update the user labels by its unique ID. \r\n\r\nLabels can be used to grant access to resources. While teams are a way for user's to share access to a resource, labels can be defined by the developer to grant access without an invitation. See the [Permissions docs](https:\/\/appwrite.io\/docs\/permissions) for more info.",
"responses": {
"200": {
"description": "User",
@@ -29661,7 +29663,7 @@
},
"\/users\/{userId}\/mfa\/authenticators\/{type}": {
"delete": {
- "summary": "Delete Authenticator",
+ "summary": "Delete authenticator",
"operationId": "usersDeleteMfaAuthenticator",
"consumes": [
"application\/json"
@@ -29737,7 +29739,7 @@
},
"\/users\/{userId}\/mfa\/factors": {
"get": {
- "summary": "List Factors",
+ "summary": "List factors",
"operationId": "usersListMfaFactors",
"consumes": [
"application\/json"
@@ -29800,7 +29802,7 @@
},
"\/users\/{userId}\/mfa\/recovery-codes": {
"get": {
- "summary": "Get MFA Recovery Codes",
+ "summary": "Get MFA recovery codes",
"operationId": "usersGetMfaRecoveryCodes",
"consumes": [
"application\/json"
@@ -29861,7 +29863,7 @@
]
},
"put": {
- "summary": "Regenerate MFA Recovery Codes",
+ "summary": "Regenerate MFA recovery codes",
"operationId": "usersUpdateMfaRecoveryCodes",
"consumes": [
"application\/json"
@@ -29922,7 +29924,7 @@
]
},
"patch": {
- "summary": "Create MFA Recovery Codes",
+ "summary": "Create MFA recovery codes",
"operationId": "usersCreateMfaRecoveryCodes",
"consumes": [
"application\/json"
@@ -30442,7 +30444,7 @@
"tags": [
"users"
],
- "description": "Creates a session for a user. Returns an immediately usable session object.\n\nIf you want to generate a token for a custom authentication flow, use the [POST \/users\/{userId}\/tokens](https:\/\/appwrite.io\/docs\/server\/users#createToken) endpoint.",
+ "description": "Creates a session for a user. Returns an immediately usable session object.\r\n\r\nIf you want to generate a token for a custom authentication flow, use the [POST \/users\/{userId}\/tokens](https:\/\/appwrite.io\/docs\/server\/users#createToken) endpoint.",
"responses": {
"201": {
"description": "Session",
@@ -30697,7 +30699,7 @@
},
"\/users\/{userId}\/targets": {
"get": {
- "summary": "List User Targets",
+ "summary": "List user targets",
"operationId": "usersListTargets",
"consumes": [
"application\/json"
@@ -30771,7 +30773,7 @@
]
},
"post": {
- "summary": "Create User Target",
+ "summary": "Create user target",
"operationId": "usersCreateTarget",
"consumes": [
"application\/json"
@@ -30886,7 +30888,7 @@
},
"\/users\/{userId}\/targets\/{targetId}": {
"get": {
- "summary": "Get User Target",
+ "summary": "Get user target",
"operationId": "usersGetTarget",
"consumes": [
"application\/json"
@@ -30956,7 +30958,7 @@
]
},
"patch": {
- "summary": "Update User target",
+ "summary": "Update user target",
"operationId": "usersUpdateTarget",
"consumes": [
"application\/json"
@@ -31133,7 +31135,7 @@
"tags": [
"users"
],
- "description": "Returns a token with a secret key for creating a session. Use the user ID and secret and submit a request to the [PUT \/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process.\n",
+ "description": "Returns a token with a secret key for creating a session. Use the user ID and secret and submit a request to the [PUT \/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process.\r\n",
"responses": {
"201": {
"description": "Token",
@@ -31368,7 +31370,7 @@
},
"\/vcs\/github\/installations\/{installationId}\/providerRepositories": {
"get": {
- "summary": "List Repositories",
+ "summary": "List repositories",
"operationId": "vcsListRepositories",
"consumes": [
"application\/json"
@@ -31594,7 +31596,7 @@
},
"\/vcs\/github\/installations\/{installationId}\/providerRepositories\/{providerRepositoryId}\/branches": {
"get": {
- "summary": "List Repository Branches",
+ "summary": "List repository branches",
"operationId": "vcsListRepositoryBranches",
"consumes": [
"application\/json"
@@ -32046,7 +32048,7 @@
]
},
"delete": {
- "summary": "Delete Installation",
+ "summary": "Delete installation",
"operationId": "vcsDeleteInstallation",
"consumes": [
"application\/json"
@@ -33456,6 +33458,16 @@
"x-example": false,
"x-nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"size": {
"type": "integer",
"description": "Attribute size.",
@@ -33475,6 +33487,8 @@
"status",
"error",
"required",
+ "$createdAt",
+ "$updatedAt",
"size"
]
},
@@ -33513,6 +33527,16 @@
"x-example": false,
"x-nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"min": {
"type": "integer",
"description": "Minimum value to enforce for new documents.",
@@ -33540,7 +33564,9 @@
"type",
"status",
"error",
- "required"
+ "required",
+ "$createdAt",
+ "$updatedAt"
]
},
"attributeFloat": {
@@ -33578,6 +33604,16 @@
"x-example": false,
"x-nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"min": {
"type": "number",
"description": "Minimum value to enforce for new documents.",
@@ -33605,7 +33641,9 @@
"type",
"status",
"error",
- "required"
+ "required",
+ "$createdAt",
+ "$updatedAt"
]
},
"attributeBoolean": {
@@ -33643,6 +33681,16 @@
"x-example": false,
"x-nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"default": {
"type": "boolean",
"description": "Default value for attribute when not provided. Cannot be set when attribute is required.",
@@ -33655,7 +33703,9 @@
"type",
"status",
"error",
- "required"
+ "required",
+ "$createdAt",
+ "$updatedAt"
]
},
"attributeEmail": {
@@ -33693,6 +33743,16 @@
"x-example": false,
"x-nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"format": {
"type": "string",
"description": "String format.",
@@ -33711,6 +33771,8 @@
"status",
"error",
"required",
+ "$createdAt",
+ "$updatedAt",
"format"
]
},
@@ -33749,6 +33811,16 @@
"x-example": false,
"x-nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"elements": {
"type": "array",
"description": "Array of elements in enumerated type.",
@@ -33775,6 +33847,8 @@
"status",
"error",
"required",
+ "$createdAt",
+ "$updatedAt",
"elements",
"format"
]
@@ -33814,6 +33888,16 @@
"x-example": false,
"x-nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"format": {
"type": "string",
"description": "String format.",
@@ -33832,6 +33916,8 @@
"status",
"error",
"required",
+ "$createdAt",
+ "$updatedAt",
"format"
]
},
@@ -33870,6 +33956,16 @@
"x-example": false,
"x-nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"format": {
"type": "string",
"description": "String format.",
@@ -33888,6 +33984,8 @@
"status",
"error",
"required",
+ "$createdAt",
+ "$updatedAt",
"format"
]
},
@@ -33926,6 +34024,16 @@
"x-example": false,
"x-nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"format": {
"type": "string",
"description": "ISO 8601 format.",
@@ -33944,6 +34052,8 @@
"status",
"error",
"required",
+ "$createdAt",
+ "$updatedAt",
"format"
]
},
@@ -33982,6 +34092,16 @@
"x-example": false,
"x-nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"relatedCollection": {
"type": "string",
"description": "The ID of the related collection.",
@@ -34019,6 +34139,8 @@
"status",
"error",
"required",
+ "$createdAt",
+ "$updatedAt",
"relatedCollection",
"relationType",
"twoWay",
@@ -34067,6 +34189,16 @@
},
"x-example": [],
"x-nullable": true
+ },
+ "$createdAt": {
+ "type": "string",
+ "description": "Index creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Index update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
}
},
"required": [
@@ -34074,7 +34206,9 @@
"type",
"status",
"error",
- "attributes"
+ "attributes",
+ "$createdAt",
+ "$updatedAt"
]
},
"document": {
@@ -36104,9 +36238,9 @@
"format": "int32"
},
"responseBody": {
- "type": "string",
+ "type": "payload",
"description": "HTTP response body. This will return empty unless execution is created as synchronous.",
- "x-example": "Developers are awesome."
+ "x-example": ""
},
"responseHeaders": {
"type": "array",
@@ -37270,6 +37404,12 @@
"x-example": 0,
"format": "int32"
},
+ "storageTotal": {
+ "type": "integer",
+ "description": "Total aggregated number of total databases storage in bytes.",
+ "x-example": 0,
+ "format": "int32"
+ },
"databases": {
"type": "array",
"description": "Aggregated number of databases per period.",
@@ -37296,6 +37436,15 @@
"$ref": "#\/definitions\/metric"
},
"x-example": []
+ },
+ "storage": {
+ "type": "array",
+ "description": "An array of the aggregated number of databases storage in bytes per period.",
+ "items": {
+ "type": "object",
+ "$ref": "#\/definitions\/metric"
+ },
+ "x-example": []
}
},
"required": [
@@ -37303,9 +37452,11 @@
"databasesTotal",
"collectionsTotal",
"documentsTotal",
+ "storageTotal",
"databases",
"collections",
- "documents"
+ "documents",
+ "storage"
]
},
"usageDatabase": {
@@ -37329,6 +37480,12 @@
"x-example": 0,
"format": "int32"
},
+ "storageTotal": {
+ "type": "integer",
+ "description": "Total aggregated number of total storage used in bytes.",
+ "x-example": 0,
+ "format": "int32"
+ },
"collections": {
"type": "array",
"description": "Aggregated number of collections per period.",
@@ -37346,14 +37503,25 @@
"$ref": "#\/definitions\/metric"
},
"x-example": []
+ },
+ "storage": {
+ "type": "array",
+ "description": "Aggregated storage used in bytes per period.",
+ "items": {
+ "type": "object",
+ "$ref": "#\/definitions\/metric"
+ },
+ "x-example": []
}
},
"required": [
"range",
"collectionsTotal",
"documentsTotal",
+ "storageTotal",
"collections",
- "documents"
+ "documents",
+ "storage"
]
},
"usageCollection": {
@@ -37921,6 +38089,12 @@
"x-example": 0,
"format": "int32"
},
+ "databasesStorageTotal": {
+ "type": "integer",
+ "description": "Total aggregated sum of databases storage size (in bytes).",
+ "x-example": 0,
+ "format": "int32"
+ },
"usersTotal": {
"type": "integer",
"description": "Total aggregated number of users.",
@@ -38023,6 +38197,15 @@
},
"x-example": []
},
+ "databasesStorageBreakdown": {
+ "type": "array",
+ "description": "An array of the aggregated breakdown of storage usage by databases.",
+ "items": {
+ "type": "object",
+ "$ref": "#\/definitions\/metricBreakdown"
+ },
+ "x-example": []
+ },
"executionsMbSecondsBreakdown": {
"type": "array",
"description": "Aggregated breakdown in totals of execution mbSeconds by functions.",
@@ -38055,6 +38238,7 @@
"executionsTotal",
"documentsTotal",
"databasesTotal",
+ "databasesStorageTotal",
"usersTotal",
"filesStorageTotal",
"functionsStorageTotal",
@@ -38069,6 +38253,7 @@
"executions",
"executionsBreakdown",
"bucketsBreakdown",
+ "databasesStorageBreakdown",
"executionsMbSecondsBreakdown",
"buildsMbSecondsBreakdown",
"functionsStorageBreakdown"
diff --git a/app/config/specs/swagger2-latest-server.json b/app/config/specs/swagger2-latest-server.json
index af6274226f..80c9e6037f 100644
--- a/app/config/specs/swagger2-latest-server.json
+++ b/app/config/specs/swagger2-latest-server.json
@@ -238,7 +238,7 @@
"tags": [
"account"
],
- "description": "Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. For security measures, user password is required to complete this request.\nThis endpoint can also be used to convert an anonymous account to a normal one, by passing an email address and a new password.\n",
+ "description": "Update currently logged in user account email address. After changing user address, the user confirmation status will get reset. A new confirmation email is not sent automatically however you can use the send confirmation email endpoint again to send the confirmation email. For security measures, user password is required to complete this request.\r\nThis endpoint can also be used to convert an anonymous account to a normal one, by passing an email address and a new password.\r\n",
"responses": {
"200": {
"description": "User",
@@ -310,7 +310,7 @@
},
"\/account\/identities": {
"get": {
- "summary": "List Identities",
+ "summary": "List identities",
"operationId": "accountListIdentities",
"consumes": [
"application\/json"
@@ -640,7 +640,7 @@
},
"\/account\/mfa\/authenticators\/{type}": {
"post": {
- "summary": "Create Authenticator",
+ "summary": "Create authenticator",
"operationId": "accountCreateMfaAuthenticator",
"consumes": [
"application\/json"
@@ -709,7 +709,7 @@
]
},
"put": {
- "summary": "Verify Authenticator",
+ "summary": "Verify authenticator",
"operationId": "accountUpdateMfaAuthenticator",
"consumes": [
"application\/json"
@@ -796,7 +796,7 @@
]
},
"delete": {
- "summary": "Delete Authenticator",
+ "summary": "Delete authenticator",
"operationId": "accountDeleteMfaAuthenticator",
"consumes": [
"application\/json"
@@ -862,7 +862,7 @@
},
"\/account\/mfa\/challenge": {
"post": {
- "summary": "Create MFA Challenge",
+ "summary": "Create MFA challenge",
"operationId": "accountCreateMfaChallenge",
"consumes": [
"application\/json"
@@ -941,7 +941,7 @@
]
},
"put": {
- "summary": "Create MFA Challenge (confirmation)",
+ "summary": "Create MFA challenge (confirmation)",
"operationId": "accountUpdateMfaChallenge",
"consumes": [
"application\/json"
@@ -1019,7 +1019,7 @@
},
"\/account\/mfa\/factors": {
"get": {
- "summary": "List Factors",
+ "summary": "List factors",
"operationId": "accountListMfaFactors",
"consumes": [
"application\/json"
@@ -1075,7 +1075,7 @@
},
"\/account\/mfa\/recovery-codes": {
"get": {
- "summary": "Get MFA Recovery Codes",
+ "summary": "Get MFA recovery codes",
"operationId": "accountGetMfaRecoveryCodes",
"consumes": [
"application\/json"
@@ -1129,7 +1129,7 @@
]
},
"post": {
- "summary": "Create MFA Recovery Codes",
+ "summary": "Create MFA recovery codes",
"operationId": "accountCreateMfaRecoveryCodes",
"consumes": [
"application\/json"
@@ -1183,7 +1183,7 @@
]
},
"patch": {
- "summary": "Regenerate MFA Recovery Codes",
+ "summary": "Regenerate MFA recovery codes",
"operationId": "accountUpdateMfaRecoveryCodes",
"consumes": [
"application\/json"
@@ -1705,7 +1705,7 @@
"tags": [
"account"
],
- "description": "Use this endpoint to complete the user account password reset. Both the **userId** and **secret** arguments will be passed as query parameters to the redirect URL you have provided when sending your request to the [POST \/account\/recovery](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createRecovery) endpoint.\n\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.",
+ "description": "Use this endpoint to complete the user account password reset. Both the **userId** and **secret** arguments will be passed as query parameters to the redirect URL you have provided when sending your request to the [POST \/account\/recovery](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createRecovery) endpoint.\r\n\r\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.",
"responses": {
"200": {
"description": "Token",
@@ -1953,7 +1953,7 @@
"tags": [
"account"
],
- "description": "Allow the user to login into their account by providing a valid email and password combination. This route will create a new session for the user.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
+ "description": "Allow the user to login into their account by providing a valid email and password combination. This route will create a new session for the user.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
"responses": {
"201": {
"description": "Session",
@@ -2518,7 +2518,7 @@
"tags": [
"account"
],
- "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's email is valid for 15 minutes.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
+ "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's email is valid for 15 minutes.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
"responses": {
"201": {
"description": "Token",
@@ -2604,7 +2604,7 @@
"tags": [
"account"
],
- "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour. If you are on a mobile device you can leave the URL parameter empty, so that the login completion will be handled by your Appwrite instance by default.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\n",
+ "description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour. If you are on a mobile device you can leave the URL parameter empty, so that the login completion will be handled by your Appwrite instance by default.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\r\n",
"responses": {
"201": {
"description": "Token",
@@ -2699,7 +2699,7 @@
"tags": [
"account"
],
- "description": "Allow the user to login to their account using the OAuth2 provider of their choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed. \n\nIf authentication succeeds, `userId` and `secret` of a token will be appended to the success URL as query parameters. These can be used to create a new session using the [Create session](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
+ "description": "Allow the user to login to their account using the OAuth2 provider of their choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed. \r\n\r\nIf authentication succeeds, `userId` and `secret` of a token will be appended to the success URL as query parameters. These can be used to create a new session using the [Create session](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
"responses": {
"301": {
"description": "No content"
@@ -2834,7 +2834,7 @@
"tags": [
"account"
],
- "description": "Sends the user an SMS with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's phone is valid for 15 minutes.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
+ "description": "Sends the user an SMS with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The secret sent to the user's phone is valid for 15 minutes.\r\n\r\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).",
"responses": {
"201": {
"description": "Token",
@@ -2917,7 +2917,7 @@
"tags": [
"account"
],
- "description": "Use this endpoint to send a verification message to your user email address to confirm they are the valid owners of that address. Both the **userId** and **secret** arguments will be passed as query parameters to the URL you have provided to be attached to the verification email. The provided URL should redirect the user back to your app and allow you to complete the verification process by verifying both the **userId** and **secret** parameters. Learn more about how to [complete the verification process](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\n\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.\n",
+ "description": "Use this endpoint to send a verification message to your user email address to confirm they are the valid owners of that address. Both the **userId** and **secret** arguments will be passed as query parameters to the URL you have provided to be attached to the verification email. The provided URL should redirect the user back to your app and allow you to complete the verification process by verifying both the **userId** and **secret** parameters. Learn more about how to [complete the verification process](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#updateVerification). The verification link sent to the user's email address is valid for 7 days.\r\n\r\nPlease note that in order to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.\r\n",
"responses": {
"201": {
"description": "Token",
@@ -3214,7 +3214,7 @@
"tags": [
"avatars"
],
- "description": "You can use this endpoint to show different browser icons to your users. The code argument receives the browser code as it appears in your user [GET \/account\/sessions](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#getSessions) endpoint. Use width, height and quality arguments to change the output settings.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.",
+ "description": "You can use this endpoint to show different browser icons to your users. The code argument receives the browser code as it appears in your user [GET \/account\/sessions](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#getSessions) endpoint. Use width, height and quality arguments to change the output settings.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.",
"responses": {
"200": {
"description": "Image",
@@ -3345,7 +3345,7 @@
"tags": [
"avatars"
],
- "description": "The credit card endpoint will return you the icon of the credit card provider you need. Use width, height and quality arguments to change the output settings.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n",
+ "description": "The credit card endpoint will return you the icon of the credit card provider you need. Use width, height and quality arguments to change the output settings.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n",
"responses": {
"200": {
"description": "Image",
@@ -3480,7 +3480,7 @@
"tags": [
"avatars"
],
- "description": "Use this endpoint to fetch the favorite icon (AKA favicon) of any remote website URL.\n\nThis endpoint does not follow HTTP redirects.",
+ "description": "Use this endpoint to fetch the favorite icon (AKA favicon) of any remote website URL.\r\n\r\nThis endpoint does not follow HTTP redirects.",
"responses": {
"200": {
"description": "Image",
@@ -3549,7 +3549,7 @@
"tags": [
"avatars"
],
- "description": "You can use this endpoint to show different country flags icons to your users. The code argument receives the 2 letter country code. Use width, height and quality arguments to change the output settings. Country codes follow the [ISO 3166-1](https:\/\/en.wikipedia.org\/wiki\/ISO_3166-1) standard.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n",
+ "description": "You can use this endpoint to show different country flags icons to your users. The code argument receives the 2 letter country code. Use width, height and quality arguments to change the output settings. Country codes follow the [ISO 3166-1](https:\/\/en.wikipedia.org\/wiki\/ISO_3166-1) standard.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n",
"responses": {
"200": {
"description": "Image",
@@ -4042,7 +4042,7 @@
"tags": [
"avatars"
],
- "description": "Use this endpoint to fetch a remote image URL and crop it to any image size you want. This endpoint is very useful if you need to crop and display remote images in your app or in case you want to make sure a 3rd party image is properly served using a TLS protocol.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 400x400px.\n\nThis endpoint does not follow HTTP redirects.",
+ "description": "Use this endpoint to fetch a remote image URL and crop it to any image size you want. This endpoint is very useful if you need to crop and display remote images in your app or in case you want to make sure a 3rd party image is properly served using a TLS protocol.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 400x400px.\r\n\r\nThis endpoint does not follow HTTP redirects.",
"responses": {
"200": {
"description": "Image",
@@ -4131,7 +4131,7 @@
"tags": [
"avatars"
],
- "description": "Use this endpoint to show your user initials avatar icon on your website or app. By default, this route will try to print your logged-in user name or email initials. You can also overwrite the user name if you pass the 'name' parameter. If no name is given and no user is logged, an empty avatar will be returned.\n\nYou can use the color and background params to change the avatar colors. By default, a random theme will be selected. The random theme will persist for the user's initials when reloading the same theme will always return for the same initials.\n\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\n",
+ "description": "Use this endpoint to show your user initials avatar icon on your website or app. By default, this route will try to print your logged-in user name or email initials. You can also overwrite the user name if you pass the 'name' parameter. If no name is given and no user is logged, an empty avatar will be returned.\r\n\r\nYou can use the color and background params to change the avatar colors. By default, a random theme will be selected. The random theme will persist for the user's initials when reloading the same theme will always return for the same initials.\r\n\r\nWhen one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.\r\n",
"responses": {
"200": {
"description": "Image",
@@ -4228,7 +4228,7 @@
"tags": [
"avatars"
],
- "description": "Converts a given plain text to a QR code image. You can use the query parameters to change the size and style of the resulting image.\n",
+ "description": "Converts a given plain text to a QR code image. You can use the query parameters to change the size and style of the resulting image.\r\n",
"responses": {
"200": {
"description": "Image",
@@ -4400,7 +4400,7 @@
"tags": [
"databases"
],
- "description": "Create a new Database.\n",
+ "description": "Create a new Database.\r\n",
"responses": {
"201": {
"description": "Database",
@@ -5217,7 +5217,7 @@
"tags": [
"databases"
],
- "description": "Create a boolean attribute.\n",
+ "description": "Create a boolean attribute.\r\n",
"responses": {
"202": {
"description": "AttributeBoolean",
@@ -5653,7 +5653,7 @@
"tags": [
"databases"
],
- "description": "Create an email attribute.\n",
+ "description": "Create an email attribute.\r\n",
"responses": {
"202": {
"description": "AttributeEmail",
@@ -5760,7 +5760,7 @@
"tags": [
"databases"
],
- "description": "Update an email attribute. Changing the `default` value will not update already existing documents.\n",
+ "description": "Update an email attribute. Changing the `default` value will not update already existing documents.\r\n",
"responses": {
"200": {
"description": "AttributeEmail",
@@ -5871,7 +5871,7 @@
"tags": [
"databases"
],
- "description": "Create an enumeration attribute. The `elements` param acts as a white-list of accepted values for this attribute. \n",
+ "description": "Create an enumeration attribute. The `elements` param acts as a white-list of accepted values for this attribute. \r\n",
"responses": {
"202": {
"description": "AttributeEnum",
@@ -5988,7 +5988,7 @@
"tags": [
"databases"
],
- "description": "Update an enum attribute. Changing the `default` value will not update already existing documents.\n",
+ "description": "Update an enum attribute. Changing the `default` value will not update already existing documents.\r\n",
"responses": {
"200": {
"description": "AttributeEnum",
@@ -6109,7 +6109,7 @@
"tags": [
"databases"
],
- "description": "Create a float attribute. Optionally, minimum and maximum values can be provided.\n",
+ "description": "Create a float attribute. Optionally, minimum and maximum values can be provided.\r\n",
"responses": {
"202": {
"description": "AttributeFloat",
@@ -6228,7 +6228,7 @@
"tags": [
"databases"
],
- "description": "Update a float attribute. Changing the `default` value will not update already existing documents.\n",
+ "description": "Update a float attribute. Changing the `default` value will not update already existing documents.\r\n",
"responses": {
"200": {
"description": "AttributeFloat",
@@ -6353,7 +6353,7 @@
"tags": [
"databases"
],
- "description": "Create an integer attribute. Optionally, minimum and maximum values can be provided.\n",
+ "description": "Create an integer attribute. Optionally, minimum and maximum values can be provided.\r\n",
"responses": {
"202": {
"description": "AttributeInteger",
@@ -6472,7 +6472,7 @@
"tags": [
"databases"
],
- "description": "Update an integer attribute. Changing the `default` value will not update already existing documents.\n",
+ "description": "Update an integer attribute. Changing the `default` value will not update already existing documents.\r\n",
"responses": {
"200": {
"description": "AttributeInteger",
@@ -6597,7 +6597,7 @@
"tags": [
"databases"
],
- "description": "Create IP address attribute.\n",
+ "description": "Create IP address attribute.\r\n",
"responses": {
"202": {
"description": "AttributeIP",
@@ -6704,7 +6704,7 @@
"tags": [
"databases"
],
- "description": "Update an ip attribute. Changing the `default` value will not update already existing documents.\n",
+ "description": "Update an ip attribute. Changing the `default` value will not update already existing documents.\r\n",
"responses": {
"200": {
"description": "AttributeIP",
@@ -6815,7 +6815,7 @@
"tags": [
"databases"
],
- "description": "Create relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\n",
+ "description": "Create relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\r\n",
"responses": {
"202": {
"description": "AttributeRelationship",
@@ -6951,7 +6951,7 @@
"tags": [
"databases"
],
- "description": "Create a string attribute.\n",
+ "description": "Create a string attribute.\r\n",
"responses": {
"202": {
"description": "AttributeString",
@@ -7071,7 +7071,7 @@
"tags": [
"databases"
],
- "description": "Update a string attribute. Changing the `default` value will not update already existing documents.\n",
+ "description": "Update a string attribute. Changing the `default` value will not update already existing documents.\r\n",
"responses": {
"200": {
"description": "AttributeString",
@@ -7188,7 +7188,7 @@
"tags": [
"databases"
],
- "description": "Create a URL attribute.\n",
+ "description": "Create a URL attribute.\r\n",
"responses": {
"202": {
"description": "AttributeURL",
@@ -7295,7 +7295,7 @@
"tags": [
"databases"
],
- "description": "Update an url attribute. Changing the `default` value will not update already existing documents.\n",
+ "description": "Update an url attribute. Changing the `default` value will not update already existing documents.\r\n",
"responses": {
"200": {
"description": "AttributeURL",
@@ -7586,7 +7586,7 @@
"tags": [
"databases"
],
- "description": "Update relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\n",
+ "description": "Update relationship attribute. [Learn more about relationship attributes](https:\/\/appwrite.io\/docs\/databases-relationships#relationship-attributes).\r\n",
"responses": {
"200": {
"description": "AttributeRelationship",
@@ -8250,7 +8250,7 @@
"tags": [
"databases"
],
- "description": "Creates an index on the attributes listed. Your index should include all the attributes you will query in a single request.\nAttributes can be `key`, `fulltext`, and `unique`.",
+ "description": "Creates an index on the attributes listed. Your index should include all the attributes you will query in a single request.\r\nAttributes can be `key`, `fulltext`, and `unique`.",
"responses": {
"202": {
"description": "Index",
@@ -8917,7 +8917,7 @@
"tags": [
"functions"
],
- "description": "List allowed function specifications for this instance.\n",
+ "description": "List allowed function specifications for this instance.\r\n",
"responses": {
"200": {
"description": "Specifications List",
@@ -9415,7 +9415,7 @@
"tags": [
"functions"
],
- "description": "Create a new function code deployment. Use this endpoint to upload a new version of your code function. To execute your newly uploaded code, you'll need to update the function's deployment to use your new deployment UID.\n\nThis endpoint accepts a tar.gz file compressed with your code. Make sure to include any dependencies your code has within the compressed file. You can learn more about code packaging in the [Appwrite Cloud Functions tutorial](https:\/\/appwrite.io\/docs\/functions).\n\nUse the \"command\" param to set the entrypoint used to execute your code.",
+ "description": "Create a new function code deployment. Use this endpoint to upload a new version of your code function. To execute your newly uploaded code, you'll need to update the function's deployment to use your new deployment UID.\r\n\r\nThis endpoint accepts a tar.gz file compressed with your code. Make sure to include any dependencies your code has within the compressed file. You can learn more about code packaging in the [Appwrite Cloud Functions tutorial](https:\/\/appwrite.io\/docs\/functions).\r\n\r\nUse the \"command\" param to set the entrypoint used to execute your code.",
"responses": {
"202": {
"description": "Deployment",
@@ -10231,7 +10231,7 @@
"tags": [
"functions"
],
- "description": "Delete a function execution by its unique ID.\n",
+ "description": "Delete a function execution by its unique ID.\r\n",
"responses": {
"204": {
"description": "No content"
@@ -11494,7 +11494,7 @@
"tags": [
"health"
],
- "description": "Returns the amount of failed jobs in a given queue.\n",
+ "description": "Returns the amount of failed jobs in a given queue.\r\n",
"responses": {
"200": {
"description": "Health Queue",
@@ -12265,7 +12265,7 @@
"tags": [
"locale"
],
- "description": "Get the current user location based on IP. Returns an object with user country code, country name, continent name, continent code, ip address and suggested currency. You can use the locale header to get the data in a supported language.\n\n([IP Geolocation by DB-IP](https:\/\/db-ip.com))",
+ "description": "Get the current user location based on IP. Returns an object with user country code, country name, continent name, continent code, ip address and suggested currency. You can use the locale header to get the data in a supported language.\r\n\r\n([IP Geolocation by DB-IP](https:\/\/db-ip.com))",
"responses": {
"200": {
"description": "Locale",
@@ -12312,7 +12312,7 @@
},
"\/locale\/codes": {
"get": {
- "summary": "List Locale Codes",
+ "summary": "List locale codes",
"operationId": "localeListCodes",
"consumes": [
"application\/json"
@@ -12968,7 +12968,7 @@
"tags": [
"messaging"
],
- "description": "Update an email message by its unique ID.\n",
+ "description": "Update an email message by its unique ID.\r\n",
"responses": {
"200": {
"description": "Message",
@@ -13302,7 +13302,7 @@
"tags": [
"messaging"
],
- "description": "Update a push notification by its unique ID.\n",
+ "description": "Update a push notification by its unique ID.\r\n",
"responses": {
"200": {
"description": "Message",
@@ -13596,7 +13596,7 @@
"tags": [
"messaging"
],
- "description": "Update an email message by its unique ID.\n",
+ "description": "Update an email message by its unique ID.\r\n",
"responses": {
"200": {
"description": "Message",
@@ -13715,7 +13715,7 @@
"tags": [
"messaging"
],
- "description": "Get a message by its unique ID.\n",
+ "description": "Get a message by its unique ID.\r\n",
"responses": {
"200": {
"description": "Message",
@@ -16355,7 +16355,7 @@
"tags": [
"messaging"
],
- "description": "Get a provider by its unique ID.\n",
+ "description": "Get a provider by its unique ID.\r\n",
"responses": {
"200": {
"description": "Provider",
@@ -16803,7 +16803,7 @@
"tags": [
"messaging"
],
- "description": "Get a topic by its unique ID.\n",
+ "description": "Get a topic by its unique ID.\r\n",
"responses": {
"200": {
"description": "Topic",
@@ -16866,7 +16866,7 @@
"tags": [
"messaging"
],
- "description": "Update a topic by its unique ID.\n",
+ "description": "Update a topic by its unique ID.\r\n",
"responses": {
"200": {
"description": "Topic",
@@ -17270,7 +17270,7 @@
"tags": [
"messaging"
],
- "description": "Get a subscriber by its unique ID.\n",
+ "description": "Get a subscriber by its unique ID.\r\n",
"responses": {
"200": {
"description": "Subscriber",
@@ -17981,7 +17981,7 @@
"tags": [
"storage"
],
- "description": "Create a new file. Before using this route, you should create a new bucket resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/storage#storageCreateBucket) API or directly from your Appwrite console.\n\nLarger files should be uploaded using multiple requests with the [content-range](https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Content-Range) header to send a partial request with a maximum supported chunk of `5MB`. The `content-range` header values should always be in bytes.\n\nWhen the first request is sent, the server will return the **File** object, and the subsequent part request must include the file's **id** in `x-appwrite-id` header to allow the server to know that the partial upload is for the existing file and not for a new one.\n\nIf you're creating a new file using one of the Appwrite SDKs, all the chunking logic will be managed by the SDK internally.\n",
+ "description": "Create a new file. Before using this route, you should create a new bucket resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/storage#storageCreateBucket) API or directly from your Appwrite console.\r\n\r\nLarger files should be uploaded using multiple requests with the [content-range](https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\/Content-Range) header to send a partial request with a maximum supported chunk of `5MB`. The `content-range` header values should always be in bytes.\r\n\r\nWhen the first request is sent, the server will return the **File** object, and the subsequent part request must include the file's **id** in `x-appwrite-id` header to allow the server to know that the partial upload is for the existing file and not for a new one.\r\n\r\nIf you're creating a new file using one of the Appwrite SDKs, all the chunking logic will be managed by the SDK internally.\r\n",
"responses": {
"201": {
"description": "File",
@@ -18238,7 +18238,7 @@
]
},
"delete": {
- "summary": "Delete File",
+ "summary": "Delete file",
"operationId": "storageDeleteFile",
"consumes": [
"application\/json"
@@ -18575,7 +18575,8 @@
"jpeg",
"gif",
"png",
- "webp"
+ "webp",
+ "avif"
],
"x-enum-name": "ImageFormat",
"x-enum-keys": [],
@@ -19149,7 +19150,7 @@
"tags": [
"teams"
],
- "description": "Invite a new member to join your team. Provide an ID for existing users, or invite unregistered users using an email or phone number. If initiated from a Client SDK, Appwrite will send an email or sms with a link to join the team to the invited user, and an account will be created for them if one doesn't exist. If initiated from a Server SDK, the new member will be added automatically to the team.\n\nYou only need to provide one of a user ID, email, or phone number. Appwrite will prioritize accepting the user ID > email > phone number if you provide more than one of these parameters.\n\nUse the `url` parameter to redirect the user from the invitation email to your app. After the user is redirected, use the [Update Team Membership Status](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/teams#updateMembershipStatus) endpoint to allow the user to accept the invitation to the team. \n\nPlease note that to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) Appwrite will accept the only redirect URLs under the domains you have added as a platform on the Appwrite Console.\n",
+ "description": "Invite a new member to join your team. Provide an ID for existing users, or invite unregistered users using an email or phone number. If initiated from a Client SDK, Appwrite will send an email or sms with a link to join the team to the invited user, and an account will be created for them if one doesn't exist. If initiated from a Server SDK, the new member will be added automatically to the team.\r\n\r\nYou only need to provide one of a user ID, email, or phone number. Appwrite will prioritize accepting the user ID > email > phone number if you provide more than one of these parameters.\r\n\r\nUse the `url` parameter to redirect the user from the invitation email to your app. After the user is redirected, use the [Update Team Membership Status](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/teams#updateMembershipStatus) endpoint to allow the user to accept the invitation to the team. \r\n\r\nPlease note that to avoid a [Redirect Attack](https:\/\/github.com\/OWASP\/CheatSheetSeries\/blob\/master\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) Appwrite will accept the only redirect URLs under the domains you have added as a platform on the Appwrite Console.\r\n",
"responses": {
"201": {
"description": "Membership",
@@ -19342,7 +19343,7 @@
"tags": [
"teams"
],
- "description": "Modify the roles of a team member. Only team members with the owner role have access to this endpoint. Learn more about [roles and permissions](https:\/\/appwrite.io\/docs\/permissions).\n",
+ "description": "Modify the roles of a team member. Only team members with the owner role have access to this endpoint. Learn more about [roles and permissions](https:\/\/appwrite.io\/docs\/permissions).\r\n",
"responses": {
"200": {
"description": "Membership",
@@ -19508,7 +19509,7 @@
"tags": [
"teams"
],
- "description": "Use this endpoint to allow a user to accept an invitation to join a team after being redirected back to your app from the invitation email received by the user.\n\nIf the request is successful, a session for the user is automatically created.\n",
+ "description": "Use this endpoint to allow a user to accept an invitation to join a team after being redirected back to your app from the invitation email received by the user.\r\n\r\nIf the request is successful, a session for the user is automatically created.\r\n",
"responses": {
"200": {
"description": "Membership",
@@ -20105,7 +20106,7 @@
},
"\/users\/identities": {
"get": {
- "summary": "List Identities",
+ "summary": "List identities",
"operationId": "usersListIdentities",
"consumes": [
"application\/json"
@@ -21087,7 +21088,7 @@
"tags": [
"users"
],
- "description": "Update the user labels by its unique ID. \n\nLabels can be used to grant access to resources. While teams are a way for user's to share access to a resource, labels can be defined by the developer to grant access without an invitation. See the [Permissions docs](https:\/\/appwrite.io\/docs\/permissions) for more info.",
+ "description": "Update the user labels by its unique ID. \r\n\r\nLabels can be used to grant access to resources. While teams are a way for user's to share access to a resource, labels can be defined by the developer to grant access without an invitation. See the [Permissions docs](https:\/\/appwrite.io\/docs\/permissions) for more info.",
"responses": {
"200": {
"description": "User",
@@ -21383,7 +21384,7 @@
},
"\/users\/{userId}\/mfa\/authenticators\/{type}": {
"delete": {
- "summary": "Delete Authenticator",
+ "summary": "Delete authenticator",
"operationId": "usersDeleteMfaAuthenticator",
"consumes": [
"application\/json"
@@ -21460,7 +21461,7 @@
},
"\/users\/{userId}\/mfa\/factors": {
"get": {
- "summary": "List Factors",
+ "summary": "List factors",
"operationId": "usersListMfaFactors",
"consumes": [
"application\/json"
@@ -21524,7 +21525,7 @@
},
"\/users\/{userId}\/mfa\/recovery-codes": {
"get": {
- "summary": "Get MFA Recovery Codes",
+ "summary": "Get MFA recovery codes",
"operationId": "usersGetMfaRecoveryCodes",
"consumes": [
"application\/json"
@@ -21586,7 +21587,7 @@
]
},
"put": {
- "summary": "Regenerate MFA Recovery Codes",
+ "summary": "Regenerate MFA recovery codes",
"operationId": "usersUpdateMfaRecoveryCodes",
"consumes": [
"application\/json"
@@ -21648,7 +21649,7 @@
]
},
"patch": {
- "summary": "Create MFA Recovery Codes",
+ "summary": "Create MFA recovery codes",
"operationId": "usersCreateMfaRecoveryCodes",
"consumes": [
"application\/json"
@@ -22175,7 +22176,7 @@
"tags": [
"users"
],
- "description": "Creates a session for a user. Returns an immediately usable session object.\n\nIf you want to generate a token for a custom authentication flow, use the [POST \/users\/{userId}\/tokens](https:\/\/appwrite.io\/docs\/server\/users#createToken) endpoint.",
+ "description": "Creates a session for a user. Returns an immediately usable session object.\r\n\r\nIf you want to generate a token for a custom authentication flow, use the [POST \/users\/{userId}\/tokens](https:\/\/appwrite.io\/docs\/server\/users#createToken) endpoint.",
"responses": {
"201": {
"description": "Session",
@@ -22434,7 +22435,7 @@
},
"\/users\/{userId}\/targets": {
"get": {
- "summary": "List User Targets",
+ "summary": "List user targets",
"operationId": "usersListTargets",
"consumes": [
"application\/json"
@@ -22509,7 +22510,7 @@
]
},
"post": {
- "summary": "Create User Target",
+ "summary": "Create user target",
"operationId": "usersCreateTarget",
"consumes": [
"application\/json"
@@ -22625,7 +22626,7 @@
},
"\/users\/{userId}\/targets\/{targetId}": {
"get": {
- "summary": "Get User Target",
+ "summary": "Get user target",
"operationId": "usersGetTarget",
"consumes": [
"application\/json"
@@ -22696,7 +22697,7 @@
]
},
"patch": {
- "summary": "Update User target",
+ "summary": "Update user target",
"operationId": "usersUpdateTarget",
"consumes": [
"application\/json"
@@ -22875,7 +22876,7 @@
"tags": [
"users"
],
- "description": "Returns a token with a secret key for creating a session. Use the user ID and secret and submit a request to the [PUT \/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process.\n",
+ "description": "Returns a token with a secret key for creating a session. Use the user ID and secret and submit a request to the [PUT \/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process.\r\n",
"responses": {
"201": {
"description": "Token",
@@ -24166,6 +24167,16 @@
"x-example": false,
"x-nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"size": {
"type": "integer",
"description": "Attribute size.",
@@ -24185,6 +24196,8 @@
"status",
"error",
"required",
+ "$createdAt",
+ "$updatedAt",
"size"
]
},
@@ -24223,6 +24236,16 @@
"x-example": false,
"x-nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"min": {
"type": "integer",
"description": "Minimum value to enforce for new documents.",
@@ -24250,7 +24273,9 @@
"type",
"status",
"error",
- "required"
+ "required",
+ "$createdAt",
+ "$updatedAt"
]
},
"attributeFloat": {
@@ -24288,6 +24313,16 @@
"x-example": false,
"x-nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"min": {
"type": "number",
"description": "Minimum value to enforce for new documents.",
@@ -24315,7 +24350,9 @@
"type",
"status",
"error",
- "required"
+ "required",
+ "$createdAt",
+ "$updatedAt"
]
},
"attributeBoolean": {
@@ -24353,6 +24390,16 @@
"x-example": false,
"x-nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"default": {
"type": "boolean",
"description": "Default value for attribute when not provided. Cannot be set when attribute is required.",
@@ -24365,7 +24412,9 @@
"type",
"status",
"error",
- "required"
+ "required",
+ "$createdAt",
+ "$updatedAt"
]
},
"attributeEmail": {
@@ -24403,6 +24452,16 @@
"x-example": false,
"x-nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"format": {
"type": "string",
"description": "String format.",
@@ -24421,6 +24480,8 @@
"status",
"error",
"required",
+ "$createdAt",
+ "$updatedAt",
"format"
]
},
@@ -24459,6 +24520,16 @@
"x-example": false,
"x-nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"elements": {
"type": "array",
"description": "Array of elements in enumerated type.",
@@ -24485,6 +24556,8 @@
"status",
"error",
"required",
+ "$createdAt",
+ "$updatedAt",
"elements",
"format"
]
@@ -24524,6 +24597,16 @@
"x-example": false,
"x-nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"format": {
"type": "string",
"description": "String format.",
@@ -24542,6 +24625,8 @@
"status",
"error",
"required",
+ "$createdAt",
+ "$updatedAt",
"format"
]
},
@@ -24580,6 +24665,16 @@
"x-example": false,
"x-nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"format": {
"type": "string",
"description": "String format.",
@@ -24598,6 +24693,8 @@
"status",
"error",
"required",
+ "$createdAt",
+ "$updatedAt",
"format"
]
},
@@ -24636,6 +24733,16 @@
"x-example": false,
"x-nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"format": {
"type": "string",
"description": "ISO 8601 format.",
@@ -24654,6 +24761,8 @@
"status",
"error",
"required",
+ "$createdAt",
+ "$updatedAt",
"format"
]
},
@@ -24692,6 +24801,16 @@
"x-example": false,
"x-nullable": true
},
+ "$createdAt": {
+ "type": "string",
+ "description": "Attribute creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Attribute update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
"relatedCollection": {
"type": "string",
"description": "The ID of the related collection.",
@@ -24729,6 +24848,8 @@
"status",
"error",
"required",
+ "$createdAt",
+ "$updatedAt",
"relatedCollection",
"relationType",
"twoWay",
@@ -24777,6 +24898,16 @@
},
"x-example": [],
"x-nullable": true
+ },
+ "$createdAt": {
+ "type": "string",
+ "description": "Index creation date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
+ },
+ "$updatedAt": {
+ "type": "string",
+ "description": "Index update date in ISO 8601 format.",
+ "x-example": "2020-10-15T06:38:00.000+00:00"
}
},
"required": [
@@ -24784,7 +24915,9 @@
"type",
"status",
"error",
- "attributes"
+ "attributes",
+ "$createdAt",
+ "$updatedAt"
]
},
"document": {
@@ -26458,9 +26591,9 @@
"format": "int32"
},
"responseBody": {
- "type": "string",
+ "type": "payload",
"description": "HTTP response body. This will return empty unless execution is created as synchronous.",
- "x-example": "Developers are awesome."
+ "x-example": ""
},
"responseHeaders": {
"type": "array",
diff --git a/app/config/storage/mimes.php b/app/config/storage/mimes.php
index 5d315f45bc..df325b37e9 100644
--- a/app/config/storage/mimes.php
+++ b/app/config/storage/mimes.php
@@ -6,6 +6,8 @@ return [
'image/gif',
'image/png',
'image/webp',
+ // 'image/heic',
+ 'image/avif',
// Video Files
'video/mp4',
diff --git a/app/config/storage/outputs.php b/app/config/storage/outputs.php
index 507a9ce667..cde2a9f38a 100644
--- a/app/config/storage/outputs.php
+++ b/app/config/storage/outputs.php
@@ -6,4 +6,7 @@ return [ // Accepted outputs files
'gif' => 'image/gif',
'png' => 'image/png',
'webp' => 'image/webp',
+ // 'heic' => 'image/heic',
+ // 'heics' => 'image/heic',
+ 'avif' => 'image/avif'
];
diff --git a/app/config/variables.php b/app/config/variables.php
index 2ea3d31273..113fbae335 100644
--- a/app/config/variables.php
+++ b/app/config/variables.php
@@ -193,7 +193,7 @@ return [
'introduction' => '1.5.1',
'default' => '',
'required' => true,
- 'question' => '',
+ 'question' => 'Enter an email that will be used when registering for SSL certificates',
'filter' => ''
],
[
@@ -254,7 +254,7 @@ return [
'name' => '_APP_WORKER_PER_CORE',
'description' => 'Internal Worker per core for the API, Realtime and Executor containers. Can be configured to optimize performance.',
'introduction' => '0.13.0',
- 'default' => 2,
+ 'default' => 6,
'required' => false,
'question' => '',
'filter' => ''
diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php
index a0d5bf717f..84d2260bab 100644
--- a/app/controllers/api/account.php
+++ b/app/controllers/api/account.php
@@ -2,7 +2,6 @@
use Ahc\Jwt\JWT;
use Appwrite\Auth\Auth;
-use Appwrite\Auth\Authentication;
use Appwrite\Auth\MFA\Challenge;
use Appwrite\Auth\MFA\Type;
use Appwrite\Auth\MFA\Type\TOTP;
@@ -29,6 +28,7 @@ use Appwrite\Utopia\Database\Validator\Queries\Identities;
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response;
use MaxMind\Db\Reader;
+use Utopia\App;
use Utopia\Audit\Audit as EventAudit;
use Utopia\Config\Config;
use Utopia\Database\Database;
@@ -45,16 +45,15 @@ use Utopia\Database\Validator\Queries;
use Utopia\Database\Validator\Query\Limit;
use Utopia\Database\Validator\Query\Offset;
use Utopia\Database\Validator\UID;
-use Utopia\Http\Http;
-use Utopia\Http\Validator\ArrayList;
-use Utopia\Http\Validator\Assoc;
-use Utopia\Http\Validator\Boolean;
-use Utopia\Http\Validator\Host;
-use Utopia\Http\Validator\Text;
-use Utopia\Http\Validator\URL;
-use Utopia\Http\Validator\WhiteList;
use Utopia\Locale\Locale;
use Utopia\System\System;
+use Utopia\Validator\ArrayList;
+use Utopia\Validator\Assoc;
+use Utopia\Validator\Boolean;
+use Utopia\Validator\Host;
+use Utopia\Validator\Text;
+use Utopia\Validator\URL;
+use Utopia\Validator\WhiteList;
$oauthDefaultSuccess = '/console/auth/oauth2/success';
$oauthDefaultFailure = '/console/auth/oauth2/failure';
@@ -145,13 +144,14 @@ function sendSessionAlert(Locale $locale, Document $user, Document $project, Doc
->trigger();
};
-$createSession = function (string $userId, string $secret, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Locale $locale, Reader $geodb, Event $queueForEvents, Mail $queueForMails, Authorization $authorization, Authentication $authentication) {
- $roles = $authorization->getRoles();
+
+$createSession = function (string $userId, string $secret, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Locale $locale, Reader $geodb, Event $queueForEvents, Mail $queueForMails) {
+ $roles = Authorization::getRoles();
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
$isAppUser = Auth::isAppUser($roles);
/** @var Utopia\Database\Document $user */
- $userFromRequest = $authorization->skip(fn () => $dbForProject->getDocument('users', $userId));
+ $userFromRequest = Authorization::skip(fn () => $dbForProject->getDocument('users', $userId));
if ($userFromRequest->isEmpty()) {
throw new Exception(Exception::USER_INVALID_TOKEN);
@@ -197,7 +197,7 @@ $createSession = function (string $userId, string $secret, Request $request, Res
$detector->getDevice()
));
- $authorization->addRole(Role::user($user->getId())->toString());
+ Authorization::setRole(Role::user($user->getId())->toString());
$session = $dbForProject->createDocument('sessions', $session
->setAttribute('$permissions', [
@@ -206,7 +206,7 @@ $createSession = function (string $userId, string $secret, Request $request, Res
Permission::delete(Role::user($user->getId())),
]));
- $authorization->skip(fn () => $dbForProject->deleteDocument('tokens', $verifiedToken->getId()));
+ Authorization::skip(fn () => $dbForProject->deleteDocument('tokens', $verifiedToken->getId()));
$dbForProject->purgeCachedDocument('users', $user->getId());
// Magic URL + Email OTP
@@ -247,15 +247,15 @@ $createSession = function (string $userId, string $secret, Request $request, Res
->setParam('sessionId', $session->getId());
if (!Config::getParam('domainVerification')) {
- $response->addHeader('X-Fallback-Cookies', \json_encode([$authentication->getCookieName() => Auth::encodeSession($user->getId(), $sessionSecret)]));
+ $response->addHeader('X-Fallback-Cookies', \json_encode([Auth::$cookieName => Auth::encodeSession($user->getId(), $sessionSecret)]));
}
$expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), $duration));
$protocol = $request->getProtocol();
$response
- ->addCookie($authentication->getCookieName() . '_legacy', Auth::encodeSession($user->getId(), $sessionSecret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null)
- ->addCookie($authentication->getCookieName(), Auth::encodeSession($user->getId(), $sessionSecret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite'))
+ ->addCookie(Auth::$cookieName . '_legacy', Auth::encodeSession($user->getId(), $sessionSecret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null)
+ ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $sessionSecret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite'))
->setStatusCode(Response::STATUS_CODE_CREATED);
$countryName = $locale->getText('countries.' . strtolower($session->getAttribute('countryCode')), $locale->getText('locale.country.unknown'));
@@ -270,7 +270,7 @@ $createSession = function (string $userId, string $secret, Request $request, Res
$response->dynamic($session, Response::MODEL_SESSION);
};
-Http::post('/v1/account')
+App::post('/v1/account')
->desc('Create account')
->groups(['api', 'account', 'auth'])
->label('event', 'users.[userId].create')
@@ -298,8 +298,7 @@ Http::post('/v1/account')
->inject('dbForProject')
->inject('queueForEvents')
->inject('hooks')
- ->inject('authorization')
- ->action(function (string $userId, string $email, string $password, string $name, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Event $queueForEvents, Hooks $hooks, Authorization $authorization) {
+ ->action(function (string $userId, string $email, string $password, string $name, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Event $queueForEvents, Hooks $hooks) {
$email = \strtolower($email);
if ('console' === $project->getId()) {
@@ -377,9 +376,9 @@ Http::post('/v1/account')
'accessedAt' => DateTime::now(),
]);
$user->removeAttribute('$internalId');
- $user = $authorization->skip(fn () => $dbForProject->createDocument('users', $user));
+ $user = Authorization::skip(fn () => $dbForProject->createDocument('users', $user));
try {
- $target = $authorization->skip(fn () => $dbForProject->createDocument('targets', new Document([
+ $target = Authorization::skip(fn () => $dbForProject->createDocument('targets', new Document([
'$permissions' => [
Permission::read(Role::user($user->getId())),
Permission::update(Role::user($user->getId())),
@@ -405,9 +404,9 @@ Http::post('/v1/account')
throw new Exception(Exception::USER_ALREADY_EXISTS);
}
- $authorization->removeRole(Role::guests()->toString());
- $authorization->addRole(Role::user($user->getId())->toString());
- $authorization->addRole(Role::users()->toString());
+ Authorization::unsetRole(Role::guests()->toString());
+ Authorization::setRole(Role::user($user->getId())->toString());
+ Authorization::setRole(Role::users()->toString());
$queueForEvents->setParam('userId', $user->getId());
@@ -416,7 +415,7 @@ Http::post('/v1/account')
->dynamic($user, Response::MODEL_ACCOUNT);
});
-Http::get('/v1/account')
+App::get('/v1/account')
->desc('Get account')
->groups(['api', 'account'])
->label('scope', 'account')
@@ -439,7 +438,7 @@ Http::get('/v1/account')
$response->dynamic($user, Response::MODEL_ACCOUNT);
});
-Http::delete('/v1/account')
+App::delete('/v1/account')
->desc('Delete account')
->groups(['api', 'account'])
->label('event', 'users.[userId].delete')
@@ -487,7 +486,7 @@ Http::delete('/v1/account')
$response->noContent();
});
-Http::get('/v1/account/sessions')
+App::get('/v1/account/sessions')
->desc('List sessions')
->groups(['api', 'account'])
->label('scope', 'account')
@@ -502,16 +501,15 @@ Http::get('/v1/account/sessions')
->inject('response')
->inject('user')
->inject('locale')
- ->inject('authorization')
- ->inject('authentication')
- ->action(function (Response $response, Document $user, Locale $locale, Authorization $authorization, Authentication $authentication) {
+ ->inject('project')
+ ->action(function (Response $response, Document $user, Locale $locale, Document $project) {
- $roles = $authorization->getRoles();
+ $roles = Authorization::getRoles();
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
$isAppUser = Auth::isAppUser($roles);
$sessions = $user->getAttribute('sessions', []);
- $current = Auth::sessionVerify($sessions, $authentication->getSecret());
+ $current = Auth::sessionVerify($sessions, Auth::$secret);
foreach ($sessions as $key => $session) {/** @var Document $session */
$countryName = $locale->getText('countries.' . strtolower($session->getAttribute('countryCode')), $locale->getText('locale.country.unknown'));
@@ -529,7 +527,7 @@ Http::get('/v1/account/sessions')
]), Response::MODEL_SESSION_LIST);
});
-Http::delete('/v1/account/sessions')
+App::delete('/v1/account/sessions')
->desc('Delete sessions')
->groups(['api', 'account'])
->label('scope', 'account')
@@ -550,8 +548,7 @@ Http::delete('/v1/account/sessions')
->inject('locale')
->inject('queueForEvents')
->inject('queueForDeletes')
- ->inject('authentication')
- ->action(function (Request $request, Response $response, Document $user, Database $dbForProject, Locale $locale, Event $queueForEvents, Delete $queueForDeletes, Authentication $authentication) {
+ ->action(function (Request $request, Response $response, Document $user, Database $dbForProject, Locale $locale, Event $queueForEvents, Delete $queueForDeletes) {
$protocol = $request->getProtocol();
$sessions = $user->getAttribute('sessions', []);
@@ -567,13 +564,13 @@ Http::delete('/v1/account/sessions')
->setAttribute('current', false)
->setAttribute('countryName', $locale->getText('countries.' . strtolower($session->getAttribute('countryCode')), $locale->getText('locale.country.unknown')));
- if ($session->getAttribute('secret') == Auth::hash($authentication->getSecret())) {
+ if ($session->getAttribute('secret') == Auth::hash(Auth::$secret)) {
$session->setAttribute('current', true);
// If current session delete the cookies too
$response
- ->addCookie($authentication->getCookieName() . '_legacy', '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null)
- ->addCookie($authentication->getCookieName(), '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite'));
+ ->addCookie(Auth::$cookieName . '_legacy', '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null)
+ ->addCookie(Auth::$cookieName, '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite'));
// Use current session for events.
$queueForEvents
@@ -595,7 +592,7 @@ Http::delete('/v1/account/sessions')
$response->noContent();
});
-Http::get('/v1/account/sessions/:sessionId')
+App::get('/v1/account/sessions/:sessionId')
->desc('Get session')
->groups(['api', 'account'])
->label('scope', 'account')
@@ -612,17 +609,16 @@ Http::get('/v1/account/sessions/:sessionId')
->inject('response')
->inject('user')
->inject('locale')
- ->inject('authorization')
- ->inject('authentication')
- ->action(function (?string $sessionId, Response $response, Document $user, Locale $locale, Authorization $authorization, Authentication $authentication) {
+ ->inject('project')
+ ->action(function (?string $sessionId, Response $response, Document $user, Locale $locale, Document $project) {
- $roles = $authorization->getRoles();
+ $roles = Authorization::getRoles();
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
$isAppUser = Auth::isAppUser($roles);
$sessions = $user->getAttribute('sessions', []);
$sessionId = ($sessionId === 'current')
- ? Auth::sessionVerify($user->getAttribute('sessions'), $authentication->getSecret())
+ ? Auth::sessionVerify($user->getAttribute('sessions'), Auth::$secret)
: $sessionId;
foreach ($sessions as $session) {/** @var Document $session */
@@ -630,7 +626,7 @@ Http::get('/v1/account/sessions/:sessionId')
$countryName = $locale->getText('countries.' . strtolower($session->getAttribute('countryCode')), $locale->getText('locale.country.unknown'));
$session
- ->setAttribute('current', ($session->getAttribute('secret') == Auth::hash($authentication->getSecret())))
+ ->setAttribute('current', ($session->getAttribute('secret') == Auth::hash(Auth::$secret)))
->setAttribute('countryName', $countryName)
->setAttribute('secret', ($isPrivilegedUser || $isAppUser) ? $session->getAttribute('secret', '') : '')
;
@@ -642,7 +638,7 @@ Http::get('/v1/account/sessions/:sessionId')
throw new Exception(Exception::USER_SESSION_NOT_FOUND);
});
-Http::delete('/v1/account/sessions/:sessionId')
+App::delete('/v1/account/sessions/:sessionId')
->desc('Delete session')
->groups(['api', 'account', 'mfa'])
->label('scope', 'account')
@@ -665,12 +661,12 @@ Http::delete('/v1/account/sessions/:sessionId')
->inject('locale')
->inject('queueForEvents')
->inject('queueForDeletes')
- ->inject('authentication')
- ->action(function (?string $sessionId, ?\DateTime $requestTimestamp, Request $request, Response $response, Document $user, Database $dbForProject, Locale $locale, Event $queueForEvents, Delete $queueForDeletes, Authentication $authentication) {
+ ->inject('project')
+ ->action(function (?string $sessionId, ?\DateTime $requestTimestamp, Request $request, Response $response, Document $user, Database $dbForProject, Locale $locale, Event $queueForEvents, Delete $queueForDeletes, Document $project) {
$protocol = $request->getProtocol();
$sessionId = ($sessionId === 'current')
- ? Auth::sessionVerify($user->getAttribute('sessions'), $authentication->getSecret())
+ ? Auth::sessionVerify($user->getAttribute('sessions'), Auth::$secret)
: $sessionId;
$sessions = $user->getAttribute('sessions', []);
@@ -689,7 +685,7 @@ Http::delete('/v1/account/sessions/:sessionId')
$session->setAttribute('current', false);
- if ($session->getAttribute('secret') == Auth::hash($authentication->getSecret())) { // If current session delete the cookies too
+ if ($session->getAttribute('secret') == Auth::hash(Auth::$secret)) { // If current session delete the cookies too
$session
->setAttribute('current', true)
->setAttribute('countryName', $locale->getText('countries.' . strtolower($session->getAttribute('countryCode')), $locale->getText('locale.country.unknown')));
@@ -699,8 +695,8 @@ Http::delete('/v1/account/sessions/:sessionId')
}
$response
- ->addCookie($authentication->getCookieName() . '_legacy', '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null)
- ->addCookie($authentication->getCookieName(), '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite'));
+ ->addCookie(Auth::$cookieName . '_legacy', '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null)
+ ->addCookie(Auth::$cookieName, '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite'));
}
$dbForProject->purgeCachedDocument('users', $user->getId());
@@ -722,7 +718,7 @@ Http::delete('/v1/account/sessions/:sessionId')
throw new Exception(Exception::USER_SESSION_NOT_FOUND);
});
-Http::patch('/v1/account/sessions/:sessionId')
+App::patch('/v1/account/sessions/:sessionId')
->desc('Update session')
->groups(['api', 'account'])
->label('scope', 'account')
@@ -744,11 +740,10 @@ Http::patch('/v1/account/sessions/:sessionId')
->inject('dbForProject')
->inject('project')
->inject('queueForEvents')
- ->inject('authentication')
- ->action(function (?string $sessionId, Response $response, Document $user, Database $dbForProject, Document $project, Event $queueForEvents, Authentication $authentication) {
+ ->action(function (?string $sessionId, Response $response, Document $user, Database $dbForProject, Document $project, Event $queueForEvents) {
$sessionId = ($sessionId === 'current')
- ? Auth::sessionVerify($user->getAttribute('sessions'), $authentication->getSecret())
+ ? Auth::sessionVerify($user->getAttribute('sessions'), Auth::$secret)
: $sessionId;
$sessions = $user->getAttribute('sessions', []);
@@ -799,7 +794,7 @@ Http::patch('/v1/account/sessions/:sessionId')
return $response->dynamic($session, Response::MODEL_SESSION);
});
-Http::post('/v1/account/sessions/email')
+App::post('/v1/account/sessions/email')
->alias('/v1/account/sessions')
->desc('Create email password session')
->groups(['api', 'account', 'auth', 'session'])
@@ -830,9 +825,7 @@ Http::post('/v1/account/sessions/email')
->inject('queueForEvents')
->inject('queueForMails')
->inject('hooks')
- ->inject('authorization')
- ->inject('authentication')
- ->action(function (string $email, string $password, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Locale $locale, Reader $geodb, Event $queueForEvents, Mail $queueForMails, Hooks $hooks, Authorization $authorization, Authentication $authentication) {
+ ->action(function (string $email, string $password, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Locale $locale, Reader $geodb, Event $queueForEvents, Mail $queueForMails, Hooks $hooks) {
$email = \strtolower($email);
$protocol = $request->getProtocol();
@@ -848,7 +841,7 @@ Http::post('/v1/account/sessions/email')
throw new Exception(Exception::USER_BLOCKED); // User is in status blocked
}
- $roles = $authorization->getRoles();
+ $roles = Authorization::getRoles();
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
$isAppUser = Auth::isAppUser($roles);
@@ -879,7 +872,7 @@ Http::post('/v1/account/sessions/email')
$detector->getDevice()
));
- $authorization->addRole(Role::user($user->getId())->toString());
+ Authorization::setRole(Role::user($user->getId())->toString());
// Re-hash if not using recommended algo
if ($user->getAttribute('hash') !== Auth::DEFAULT_ALGO) {
@@ -900,15 +893,15 @@ Http::post('/v1/account/sessions/email')
if (!Config::getParam('domainVerification')) {
$response
- ->addHeader('X-Fallback-Cookies', \json_encode([$authentication->getCookieName() => Auth::encodeSession($user->getId(), $secret)]))
+ ->addHeader('X-Fallback-Cookies', \json_encode([Auth::$cookieName => Auth::encodeSession($user->getId(), $secret)]))
;
}
$expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), $duration));
$response
- ->addCookie($authentication->getCookieName() . '_legacy', Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null)
- ->addCookie($authentication->getCookieName(), Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite'))
+ ->addCookie(Auth::$cookieName . '_legacy', Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null)
+ ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite'))
->setStatusCode(Response::STATUS_CODE_CREATED)
;
@@ -936,7 +929,7 @@ Http::post('/v1/account/sessions/email')
$response->dynamic($session, Response::MODEL_SESSION);
});
-Http::post('/v1/account/sessions/anonymous')
+App::post('/v1/account/sessions/anonymous')
->desc('Create anonymous session')
->groups(['api', 'account', 'auth', 'session'])
->label('event', 'users.[userId].sessions.[sessionId].create')
@@ -962,11 +955,9 @@ Http::post('/v1/account/sessions/anonymous')
->inject('dbForProject')
->inject('geodb')
->inject('queueForEvents')
- ->inject('authorization')
- ->inject('authentication')
- ->action(function (Request $request, Response $response, Locale $locale, Document $user, Document $project, Database $dbForProject, Reader $geodb, Event $queueForEvents, Authorization $authorization, Authentication $authentication) {
+ ->action(function (Request $request, Response $response, Locale $locale, Document $user, Document $project, Database $dbForProject, Reader $geodb, Event $queueForEvents) {
$protocol = $request->getProtocol();
- $roles = $authorization->getRoles();
+ $roles = Authorization::getRoles();
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
$isAppUser = Auth::isAppUser($roles);
@@ -1012,7 +1003,7 @@ Http::post('/v1/account/sessions/anonymous')
'accessedAt' => DateTime::now(),
]);
$user->removeAttribute('$internalId');
- $authorization->skip(fn () => $dbForProject->createDocument('users', $user));
+ Authorization::skip(fn () => $dbForProject->createDocument('users', $user));
// Create session token
$duration = $project->getAttribute('auths', [])['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG;
@@ -1038,7 +1029,7 @@ Http::post('/v1/account/sessions/anonymous')
$detector->getDevice()
));
- $authorization->addRole(Role::user($user->getId())->toString());
+ Authorization::setRole(Role::user($user->getId())->toString());
$session = $dbForProject->createDocument('sessions', $session-> setAttribute('$permissions', [
Permission::read(Role::user($user->getId())),
@@ -1054,14 +1045,14 @@ Http::post('/v1/account/sessions/anonymous')
;
if (!Config::getParam('domainVerification')) {
- $response->addHeader('X-Fallback-Cookies', \json_encode([$authentication->getCookieName() => Auth::encodeSession($user->getId(), $secret)]));
+ $response->addHeader('X-Fallback-Cookies', \json_encode([Auth::$cookieName => Auth::encodeSession($user->getId(), $secret)]));
}
$expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), $duration));
$response
- ->addCookie($authentication->getCookieName() . '_legacy', Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null)
- ->addCookie($authentication->getCookieName(), Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite'))
+ ->addCookie(Auth::$cookieName . '_legacy', Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null)
+ ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite'))
->setStatusCode(Response::STATUS_CODE_CREATED)
;
@@ -1076,7 +1067,7 @@ Http::post('/v1/account/sessions/anonymous')
$response->dynamic($session, Response::MODEL_SESSION);
});
-Http::post('/v1/account/sessions/token')
+App::post('/v1/account/sessions/token')
->desc('Create session')
->label('event', 'users.[userId].sessions.[sessionId].create')
->groups(['api', 'account', 'session'])
@@ -1104,11 +1095,9 @@ Http::post('/v1/account/sessions/token')
->inject('geodb')
->inject('queueForEvents')
->inject('queueForMails')
- ->inject('authorization')
- ->inject('authentication')
->action($createSession);
-Http::get('/v1/account/sessions/oauth2/:provider')
+App::get('/v1/account/sessions/oauth2/:provider')
->desc('Create OAuth2 session')
->groups(['api', 'account'])
->label('error', __DIR__ . '/../../views/general/error.phtml')
@@ -1178,7 +1167,7 @@ Http::get('/v1/account/sessions/oauth2/:provider')
->redirect($oauth2->getLoginURL());
});
-Http::get('/v1/account/sessions/oauth2/callback/:provider/:projectId')
+App::get('/v1/account/sessions/oauth2/callback/:provider/:projectId')
->desc('OAuth2 callback')
->groups(['account'])
->label('error', __DIR__ . '/../../views/general/error.phtml')
@@ -1208,7 +1197,7 @@ Http::get('/v1/account/sessions/oauth2/callback/:provider/:projectId')
. \http_build_query($params));
});
-Http::post('/v1/account/sessions/oauth2/callback/:provider/:projectId')
+App::post('/v1/account/sessions/oauth2/callback/:provider/:projectId')
->desc('OAuth2 callback')
->groups(['account'])
->label('error', __DIR__ . '/../../views/general/error.phtml')
@@ -1239,7 +1228,7 @@ Http::post('/v1/account/sessions/oauth2/callback/:provider/:projectId')
. \http_build_query($params));
});
-Http::get('/v1/account/sessions/oauth2/:provider/redirect')
+App::get('/v1/account/sessions/oauth2/:provider/redirect')
->desc('OAuth2 redirect')
->groups(['api', 'account', 'session'])
->label('error', __DIR__ . '/../../views/general/error.phtml')
@@ -1263,9 +1252,7 @@ Http::get('/v1/account/sessions/oauth2/:provider/redirect')
->inject('dbForProject')
->inject('geodb')
->inject('queueForEvents')
- ->inject('authorization')
- ->inject('authentication')
- ->action(function (string $provider, string $code, string $state, string $error, string $error_description, Request $request, Response $response, Document $project, Document $user, Database $dbForProject, Reader $geodb, Event $queueForEvents, Authorization $authorization, Authentication $authentication) use ($oauthDefaultSuccess) {
+ ->action(function (string $provider, string $code, string $state, string $error, string $error_description, Request $request, Response $response, Document $project, Document $user, Database $dbForProject, Reader $geodb, Event $queueForEvents) use ($oauthDefaultSuccess) {
$protocol = $request->getProtocol();
$callback = $protocol . '://' . $request->getHostname() . '/v1/account/sessions/oauth2/callback/' . $provider . '/' . $project->getId();
$defaultState = ['success' => $project->getAttribute('url', ''), 'failure' => ''];
@@ -1402,7 +1389,7 @@ Http::get('/v1/account/sessions/oauth2/:provider/redirect')
}
$sessions = $user->getAttribute('sessions', []);
- $current = Auth::sessionVerify($sessions, $authentication->getSecret());
+ $current = Auth::sessionVerify($sessions, Auth::$secret);
if ($current) { // Delete current session of new one.
$currentDocument = $dbForProject->getDocument('sessions', $current);
@@ -1499,16 +1486,15 @@ Http::get('/v1/account/sessions/oauth2/:provider/redirect')
'accessedAt' => DateTime::now(),
]);
$user->removeAttribute('$internalId');
- $user = $authorization->skip(fn () => $dbForProject->createDocument('users', $user));
-
+ $userDoc = Authorization::skip(fn () => $dbForProject->createDocument('users', $user));
$dbForProject->createDocument('targets', new Document([
'$permissions' => [
Permission::read(Role::user($user->getId())),
Permission::update(Role::user($user->getId())),
Permission::delete(Role::user($user->getId())),
],
- 'userId' => $user->getId(),
- 'userInternalId' => $user->getInternalId(),
+ 'userId' => $userDoc->getId(),
+ 'userInternalId' => $userDoc->getInternalId(),
'providerType' => MESSAGE_TYPE_EMAIL,
'identifier' => $email,
]));
@@ -1518,8 +1504,8 @@ Http::get('/v1/account/sessions/oauth2/:provider/redirect')
}
}
- $authorization->addRole(Role::user($user->getId())->toString());
- $authorization->addRole(Role::users()->toString());
+ Authorization::setRole(Role::user($user->getId())->toString());
+ Authorization::setRole(Role::users()->toString());
if (false === $user->getAttribute('status')) { // Account is blocked
$failureRedirect(Exception::USER_BLOCKED); // User is in status blocked
@@ -1578,7 +1564,7 @@ Http::get('/v1/account/sessions/oauth2/:provider/redirect')
$dbForProject->updateDocument('users', $user->getId(), $user);
- $authorization->addRole(Role::user($user->getId())->toString());
+ Authorization::setRole(Role::user($user->getId())->toString());
$state['success'] = URLParser::parse($state['success']);
$query = URLParser::parseQuery($state['success']['query']);
@@ -1600,7 +1586,7 @@ Http::get('/v1/account/sessions/oauth2/:provider/redirect')
'ip' => $request->getIP(),
]);
- $authorization->addRole(Role::user($user->getId())->toString());
+ Authorization::setRole(Role::user($user->getId())->toString());
$token = $dbForProject->createDocument('tokens', $token
->setAttribute('$permissions', [
@@ -1650,7 +1636,7 @@ Http::get('/v1/account/sessions/oauth2/:provider/redirect')
$session->setAttribute('expire', $expire);
if (!Config::getParam('domainVerification')) {
- $response->addHeader('X-Fallback-Cookies', \json_encode([$authentication->getCookieName() => Auth::encodeSession($user->getId(), $secret)]));
+ $response->addHeader('X-Fallback-Cookies', \json_encode([Auth::$cookieName => Auth::encodeSession($user->getId(), $secret)]));
}
$queueForEvents
@@ -1663,13 +1649,13 @@ Http::get('/v1/account/sessions/oauth2/:provider/redirect')
if ($state['success']['path'] == $oauthDefaultSuccess) {
$query['project'] = $project->getId();
$query['domain'] = Config::getParam('cookieDomain');
- $query['key'] = $authentication->getCookieName();
+ $query['key'] = Auth::$cookieName;
$query['secret'] = Auth::encodeSession($user->getId(), $secret);
}
$response
- ->addCookie($authentication->getCookieName() . '_legacy', Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null)
- ->addCookie($authentication->getCookieName(), Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite'));
+ ->addCookie(Auth::$cookieName . '_legacy', Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null)
+ ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite'));
}
if (isset($sessionUpgrade) && $sessionUpgrade) {
@@ -1700,7 +1686,7 @@ Http::get('/v1/account/sessions/oauth2/:provider/redirect')
;
});
-Http::get('/v1/account/tokens/oauth2/:provider')
+App::get('/v1/account/tokens/oauth2/:provider')
->desc('Create OAuth2 token')
->groups(['api', 'account'])
->label('error', __DIR__ . '/../../views/general/error.phtml')
@@ -1769,7 +1755,7 @@ Http::get('/v1/account/tokens/oauth2/:provider')
->redirect($oauth2->getLoginURL());
});
-Http::post('/v1/account/tokens/magic-url')
+App::post('/v1/account/tokens/magic-url')
->alias('/v1/account/sessions/magic-url')
->desc('Create magic URL token')
->groups(['api', 'account', 'auth'])
@@ -1799,8 +1785,7 @@ Http::post('/v1/account/tokens/magic-url')
->inject('locale')
->inject('queueForEvents')
->inject('queueForMails')
- ->inject('authorization')
- ->action(function (string $userId, string $email, string $url, bool $phrase, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Locale $locale, Event $queueForEvents, Mail $queueForMails, Authorization $authorization) {
+ ->action(function (string $userId, string $email, string $url, bool $phrase, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Locale $locale, Event $queueForEvents, Mail $queueForMails) {
if (empty(System::getEnv('_APP_SMTP_HOST'))) {
throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP disabled');
}
@@ -1810,7 +1795,7 @@ Http::post('/v1/account/tokens/magic-url')
$phrase = Phrase::generate();
}
- $roles = $authorization->getRoles();
+ $roles = Authorization::getRoles();
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
$isAppUser = Auth::isAppUser($roles);
@@ -1865,7 +1850,7 @@ Http::post('/v1/account/tokens/magic-url')
]);
$user->removeAttribute('$internalId');
- $user = $authorization->skip(fn () => $dbForProject->createDocument('users', $user));
+ Authorization::skip(fn () => $dbForProject->createDocument('users', $user));
}
$tokenSecret = Auth::tokenGenerator(Auth::TOKEN_LENGTH_MAGIC_URL);
@@ -1882,7 +1867,7 @@ Http::post('/v1/account/tokens/magic-url')
'ip' => $request->getIP(),
]);
- $authorization->addRole(Role::user($user->getId())->toString());
+ Authorization::setRole(Role::user($user->getId())->toString());
$token = $dbForProject->createDocument('tokens', $token
->setAttribute('$permissions', [
@@ -2014,7 +1999,7 @@ Http::post('/v1/account/tokens/magic-url')
->dynamic($token, Response::MODEL_TOKEN);
});
-Http::post('/v1/account/tokens/email')
+App::post('/v1/account/tokens/email')
->desc('Create email token (OTP)')
->groups(['api', 'account', 'auth'])
->label('scope', 'sessions.write')
@@ -2042,8 +2027,7 @@ Http::post('/v1/account/tokens/email')
->inject('locale')
->inject('queueForEvents')
->inject('queueForMails')
- ->inject('authorization')
- ->action(function (string $userId, string $email, bool $phrase, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Locale $locale, Event $queueForEvents, Mail $queueForMails, Authorization $authorization) {
+ ->action(function (string $userId, string $email, bool $phrase, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Locale $locale, Event $queueForEvents, Mail $queueForMails) {
if (empty(System::getEnv('_APP_SMTP_HOST'))) {
throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP disabled');
}
@@ -2052,7 +2036,7 @@ Http::post('/v1/account/tokens/email')
$phrase = Phrase::generate();
}
- $roles = $authorization->getRoles();
+ $roles = Authorization::getRoles();
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
$isAppUser = Auth::isAppUser($roles);
@@ -2105,7 +2089,7 @@ Http::post('/v1/account/tokens/email')
]);
$user->removeAttribute('$internalId');
- $user = $authorization->skip(fn () => $dbForProject->createDocument('users', $user));
+ Authorization::skip(fn () => $dbForProject->createDocument('users', $user));
}
$tokenSecret = Auth::codeGenerator(6);
@@ -2122,7 +2106,7 @@ Http::post('/v1/account/tokens/email')
'ip' => $request->getIP(),
]);
- $authorization->addRole(Role::user($user->getId())->toString());
+ Authorization::setRole(Role::user($user->getId())->toString());
$token = $dbForProject->createDocument('tokens', $token
->setAttribute('$permissions', [
@@ -2244,7 +2228,7 @@ Http::post('/v1/account/tokens/email')
->dynamic($token, Response::MODEL_TOKEN);
});
-Http::put('/v1/account/sessions/magic-url')
+App::put('/v1/account/sessions/magic-url')
->desc('Update magic URL session')
->label('event', 'users.[userId].sessions.[sessionId].create')
->groups(['api', 'account', 'session'])
@@ -2273,11 +2257,9 @@ Http::put('/v1/account/sessions/magic-url')
->inject('geodb')
->inject('queueForEvents')
->inject('queueForMails')
- ->inject('authorization')
- ->inject('authentication')
->action($createSession);
-Http::put('/v1/account/sessions/phone')
+App::put('/v1/account/sessions/phone')
->desc('Update phone session')
->label('event', 'users.[userId].sessions.[sessionId].create')
->groups(['api', 'account', 'session'])
@@ -2306,11 +2288,9 @@ Http::put('/v1/account/sessions/phone')
->inject('geodb')
->inject('queueForEvents')
->inject('queueForMails')
- ->inject('authorization')
- ->inject('authentication')
->action($createSession);
-Http::post('/v1/account/tokens/phone')
+App::post('/v1/account/tokens/phone')
->alias('/v1/account/sessions/phone')
->desc('Create phone token')
->groups(['api', 'account'])
@@ -2338,13 +2318,12 @@ Http::post('/v1/account/tokens/phone')
->inject('queueForEvents')
->inject('queueForMessaging')
->inject('locale')
- ->inject('authorization')
- ->action(function (string $userId, string $phone, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Event $queueForEvents, Messaging $queueForMessaging, Locale $locale, Authorization $authorization) {
+ ->action(function (string $userId, string $phone, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Event $queueForEvents, Messaging $queueForMessaging, Locale $locale) {
if (empty(System::getEnv('_APP_SMS_PROVIDER'))) {
throw new Exception(Exception::GENERAL_PHONE_DISABLED, 'Phone provider not configured');
}
- $roles = $authorization->getRoles();
+ $roles = Authorization::getRoles();
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
$isAppUser = Auth::isAppUser($roles);
@@ -2388,9 +2367,9 @@ Http::post('/v1/account/tokens/phone')
]);
$user->removeAttribute('$internalId');
- $user = $authorization->skip(fn () => $dbForProject->createDocument('users', $user));
+ Authorization::skip(fn () => $dbForProject->createDocument('users', $user));
try {
- $target = $authorization->skip(fn () => $dbForProject->createDocument('targets', new Document([
+ $target = Authorization::skip(fn () => $dbForProject->createDocument('targets', new Document([
'$permissions' => [
Permission::read(Role::user($user->getId())),
Permission::update(Role::user($user->getId())),
@@ -2436,7 +2415,7 @@ Http::post('/v1/account/tokens/phone')
'ip' => $request->getIP(),
]);
- $authorization->addRole(Role::user($user->getId())->toString());
+ Authorization::setRole(Role::user($user->getId())->toString());
$token = $dbForProject->createDocument('tokens', $token
->setAttribute('$permissions', [
@@ -2492,7 +2471,7 @@ Http::post('/v1/account/tokens/phone')
->dynamic($token, Response::MODEL_TOKEN);
});
-Http::post('/v1/account/jwts')
+App::post('/v1/account/jwts')
->alias('/v1/account/jwt')
->desc('Create JWT')
->groups(['api', 'account', 'auth'])
@@ -2510,15 +2489,14 @@ Http::post('/v1/account/jwts')
->inject('response')
->inject('user')
->inject('dbForProject')
- ->inject('authentication')
- ->action(function (Response $response, Document $user, Database $dbForProject, Authentication $authentication) {
+ ->action(function (Response $response, Document $user, Database $dbForProject) {
$sessions = $user->getAttribute('sessions', []);
$current = new Document();
foreach ($sessions as $session) { /** @var Utopia\Database\Document $session */
- if ($session->getAttribute('secret') == Auth::hash($authentication->getSecret())) { // If current session delete the cookies too
+ if ($session->getAttribute('secret') == Auth::hash(Auth::$secret)) { // If current session delete the cookies too
$current = $session;
}
}
@@ -2537,7 +2515,7 @@ Http::post('/v1/account/jwts')
])]), Response::MODEL_JWT);
});
-Http::get('/v1/account/prefs')
+App::get('/v1/account/prefs')
->desc('Get account preferences')
->groups(['api', 'account'])
->label('scope', 'account')
@@ -2559,7 +2537,7 @@ Http::get('/v1/account/prefs')
$response->dynamic(new Document($prefs), Response::MODEL_PREFERENCES);
});
-Http::get('/v1/account/logs')
+App::get('/v1/account/logs')
->desc('List logs')
->groups(['api', 'account'])
->label('scope', 'account')
@@ -2576,8 +2554,7 @@ Http::get('/v1/account/logs')
->inject('locale')
->inject('geodb')
->inject('dbForProject')
- ->inject('authorization')
- ->action(function (array $queries, Response $response, Document $user, Locale $locale, Reader $geodb, Database $dbForProject, Authorization $authorization) {
+ ->action(function (array $queries, Response $response, Document $user, Locale $locale, Reader $geodb, Database $dbForProject) {
try {
$queries = Query::parseQueries($queries);
@@ -2589,7 +2566,7 @@ Http::get('/v1/account/logs')
$limit = $grouped['limit'] ?? APP_LIMIT_COUNT;
$offset = $grouped['offset'] ?? 0;
- $audit = new EventAudit($dbForProject, $authorization);
+ $audit = new EventAudit($dbForProject);
$logs = $audit->getLogsByUser($user->getInternalId(), $limit, $offset);
@@ -2625,101 +2602,7 @@ Http::get('/v1/account/logs')
]), Response::MODEL_LOG_LIST);
});
-Http::patch('/v1/account/email')
- ->desc('Update email')
- ->groups(['api', 'account'])
- ->label('event', 'users.[userId].update.email')
- ->label('scope', 'account')
- ->label('audits.event', 'user.update')
- ->label('audits.resource', 'user/{response.$id}')
- ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
- ->label('sdk.namespace', 'account')
- ->label('sdk.method', 'updateEmail')
- ->label('sdk.description', '/docs/references/account/update-email.md')
- ->label('sdk.response.code', Response::STATUS_CODE_OK)
- ->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
- ->label('sdk.response.model', Response::MODEL_USER)
- ->label('sdk.offline.model', '/account')
- ->label('sdk.offline.key', 'current')
- ->param('email', '', new Email(), 'User email.')
- ->param('password', '', new Password(), 'User password. Must be at least 8 chars.')
- ->inject('requestTimestamp')
- ->inject('response')
- ->inject('user')
- ->inject('dbForProject')
- ->inject('queueForEvents')
- ->inject('project')
- ->inject('hooks')
- ->inject('authorization')
- ->action(function (string $email, string $password, ?\DateTime $requestTimestamp, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Document $project, Hooks $hooks, Authorization $authorization) {
- // passwordUpdate will be empty if the user has never set a password
- $passwordUpdate = $user->getAttribute('passwordUpdate');
-
- if (
- !empty($passwordUpdate) &&
- !Auth::passwordVerify($password, $user->getAttribute('password'), $user->getAttribute('hash'), $user->getAttribute('hashOptions'))
- ) { // Double check user password
- throw new Exception(Exception::USER_INVALID_CREDENTIALS);
- }
-
- $hooks->trigger('passwordValidator', [$dbForProject, $project, $password, &$user, false]);
-
- $oldEmail = $user->getAttribute('email');
-
- $email = \strtolower($email);
-
- // Makes sure this email is not already used in another identity
- $identityWithMatchingEmail = $dbForProject->findOne('identities', [
- Query::equal('providerEmail', [$email]),
- Query::notEqual('userInternalId', $user->getInternalId()),
- ]);
- if ($identityWithMatchingEmail !== false && !$identityWithMatchingEmail->isEmpty()) {
- throw new Exception(Exception::GENERAL_BAD_REQUEST); /** Return a generic bad request to prevent exposing existing accounts */
- }
-
- $user
- ->setAttribute('email', $email)
- ->setAttribute('emailVerification', false) // After this user needs to confirm mail again
- ;
-
- if (empty($passwordUpdate)) {
- $user
- ->setAttribute('password', Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS))
- ->setAttribute('hash', Auth::DEFAULT_ALGO)
- ->setAttribute('hashOptions', Auth::DEFAULT_ALGO_OPTIONS)
- ->setAttribute('passwordUpdate', DateTime::now());
- }
-
- $target = $authorization->skip(fn () => $dbForProject->findOne('targets', [
- Query::equal('identifier', [$email]),
- ]));
-
- if ($target instanceof Document && !$target->isEmpty()) {
- throw new Exception(Exception::USER_TARGET_ALREADY_EXISTS);
- }
-
- try {
- $user = $dbForProject->withRequestTimestamp($requestTimestamp, fn () => $dbForProject->updateDocument('users', $user->getId(), $user));
- /**
- * @var Document $oldTarget
- */
- $oldTarget = $user->find('identifier', $oldEmail, 'targets');
-
- if ($oldTarget instanceof Document && !$oldTarget->isEmpty()) {
- $authorization->skip(fn () => $dbForProject->updateDocument('targets', $oldTarget->getId(), $oldTarget->setAttribute('identifier', $email)));
- }
- $dbForProject->purgeCachedDocument('users', $user->getId());
- } catch (Duplicate) {
- throw new Exception(Exception::GENERAL_BAD_REQUEST); /** Return a generic bad request to prevent exposing existing accounts */
- }
-
- $queueForEvents->setParam('userId', $user->getId());
-
- $response->dynamic($user, Response::MODEL_ACCOUNT);
- });
-
-
-Http::patch('/v1/account/name')
+App::patch('/v1/account/name')
->desc('Update name')
->groups(['api', 'account'])
->label('event', 'users.[userId].update.name')
@@ -2752,7 +2635,7 @@ Http::patch('/v1/account/name')
$response->dynamic($user, Response::MODEL_ACCOUNT);
});
-Http::patch('/v1/account/password')
+App::patch('/v1/account/password')
->desc('Update password')
->groups(['api', 'account'])
->label('event', 'users.[userId].update.password')
@@ -2822,7 +2705,99 @@ Http::patch('/v1/account/password')
$response->dynamic($user, Response::MODEL_ACCOUNT);
});
-Http::patch('/v1/account/phone')
+App::patch('/v1/account/email')
+ ->desc('Update email')
+ ->groups(['api', 'account'])
+ ->label('event', 'users.[userId].update.email')
+ ->label('scope', 'account')
+ ->label('audits.event', 'user.update')
+ ->label('audits.resource', 'user/{response.$id}')
+ ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
+ ->label('sdk.namespace', 'account')
+ ->label('sdk.method', 'updateEmail')
+ ->label('sdk.description', '/docs/references/account/update-email.md')
+ ->label('sdk.response.code', Response::STATUS_CODE_OK)
+ ->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
+ ->label('sdk.response.model', Response::MODEL_USER)
+ ->label('sdk.offline.model', '/account')
+ ->label('sdk.offline.key', 'current')
+ ->param('email', '', new Email(), 'User email.')
+ ->param('password', '', new Password(), 'User password. Must be at least 8 chars.')
+ ->inject('requestTimestamp')
+ ->inject('response')
+ ->inject('user')
+ ->inject('dbForProject')
+ ->inject('queueForEvents')
+ ->inject('project')
+ ->inject('hooks')
+ ->action(function (string $email, string $password, ?\DateTime $requestTimestamp, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Document $project, Hooks $hooks) {
+ // passwordUpdate will be empty if the user has never set a password
+ $passwordUpdate = $user->getAttribute('passwordUpdate');
+
+ if (
+ !empty($passwordUpdate) &&
+ !Auth::passwordVerify($password, $user->getAttribute('password'), $user->getAttribute('hash'), $user->getAttribute('hashOptions'))
+ ) { // Double check user password
+ throw new Exception(Exception::USER_INVALID_CREDENTIALS);
+ }
+
+ $hooks->trigger('passwordValidator', [$dbForProject, $project, $password, &$user, false]);
+
+ $oldEmail = $user->getAttribute('email');
+
+ $email = \strtolower($email);
+
+ // Makes sure this email is not already used in another identity
+ $identityWithMatchingEmail = $dbForProject->findOne('identities', [
+ Query::equal('providerEmail', [$email]),
+ Query::notEqual('userInternalId', $user->getInternalId()),
+ ]);
+ if ($identityWithMatchingEmail !== false && !$identityWithMatchingEmail->isEmpty()) {
+ throw new Exception(Exception::GENERAL_BAD_REQUEST); /** Return a generic bad request to prevent exposing existing accounts */
+ }
+
+ $user
+ ->setAttribute('email', $email)
+ ->setAttribute('emailVerification', false) // After this user needs to confirm mail again
+ ;
+
+ if (empty($passwordUpdate)) {
+ $user
+ ->setAttribute('password', Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS))
+ ->setAttribute('hash', Auth::DEFAULT_ALGO)
+ ->setAttribute('hashOptions', Auth::DEFAULT_ALGO_OPTIONS)
+ ->setAttribute('passwordUpdate', DateTime::now());
+ }
+
+ $target = Authorization::skip(fn () => $dbForProject->findOne('targets', [
+ Query::equal('identifier', [$email]),
+ ]));
+
+ if ($target instanceof Document && !$target->isEmpty()) {
+ throw new Exception(Exception::USER_TARGET_ALREADY_EXISTS);
+ }
+
+ try {
+ $user = $dbForProject->withRequestTimestamp($requestTimestamp, fn () => $dbForProject->updateDocument('users', $user->getId(), $user));
+ /**
+ * @var Document $oldTarget
+ */
+ $oldTarget = $user->find('identifier', $oldEmail, 'targets');
+
+ if ($oldTarget instanceof Document && !$oldTarget->isEmpty()) {
+ Authorization::skip(fn () => $dbForProject->updateDocument('targets', $oldTarget->getId(), $oldTarget->setAttribute('identifier', $email)));
+ }
+ $dbForProject->purgeCachedDocument('users', $user->getId());
+ } catch (Duplicate) {
+ throw new Exception(Exception::GENERAL_BAD_REQUEST); /** Return a generic bad request to prevent exposing existing accounts */
+ }
+
+ $queueForEvents->setParam('userId', $user->getId());
+
+ $response->dynamic($user, Response::MODEL_ACCOUNT);
+ });
+
+App::patch('/v1/account/phone')
->desc('Update phone')
->groups(['api', 'account'])
->label('event', 'users.[userId].update.phone')
@@ -2847,8 +2822,7 @@ Http::patch('/v1/account/phone')
->inject('queueForEvents')
->inject('project')
->inject('hooks')
- ->inject('authorization')
- ->action(function (string $phone, string $password, ?\DateTime $requestTimestamp, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Document $project, Hooks $hooks, Authorization $authorization) {
+ ->action(function (string $phone, string $password, ?\DateTime $requestTimestamp, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Document $project, Hooks $hooks) {
// passwordUpdate will be empty if the user has never set a password
$passwordUpdate = $user->getAttribute('passwordUpdate');
@@ -2861,7 +2835,7 @@ Http::patch('/v1/account/phone')
$hooks->trigger('passwordValidator', [$dbForProject, $project, $password, &$user, false]);
- $target = $authorization->skip(fn () => $dbForProject->findOne('targets', [
+ $target = Authorization::skip(fn () => $dbForProject->findOne('targets', [
Query::equal('identifier', [$phone]),
]));
@@ -2892,7 +2866,7 @@ Http::patch('/v1/account/phone')
$oldTarget = $user->find('identifier', $oldPhone, 'targets');
if ($oldTarget instanceof Document && !$oldTarget->isEmpty()) {
- $authorization->skip(fn () => $dbForProject->updateDocument('targets', $oldTarget->getId(), $oldTarget->setAttribute('identifier', $phone)));
+ Authorization::skip(fn () => $dbForProject->updateDocument('targets', $oldTarget->getId(), $oldTarget->setAttribute('identifier', $phone)));
}
$dbForProject->purgeCachedDocument('users', $user->getId());
} catch (Duplicate $th) {
@@ -2904,7 +2878,7 @@ Http::patch('/v1/account/phone')
$response->dynamic($user, Response::MODEL_ACCOUNT);
});
-Http::patch('/v1/account/prefs')
+App::patch('/v1/account/prefs')
->desc('Update preferences')
->groups(['api', 'account'])
->label('event', 'users.[userId].update.prefs')
@@ -2937,7 +2911,7 @@ Http::patch('/v1/account/prefs')
$response->dynamic($user, Response::MODEL_ACCOUNT);
});
-Http::patch('/v1/account/status')
+App::patch('/v1/account/status')
->desc('Update status')
->groups(['api', 'account'])
->label('event', 'users.[userId].update.status')
@@ -2957,8 +2931,7 @@ Http::patch('/v1/account/status')
->inject('user')
->inject('dbForProject')
->inject('queueForEvents')
- ->inject('authentication')
- ->action(function (?\DateTime $requestTimestamp, Request $request, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Authentication $authentication) {
+ ->action(function (?\DateTime $requestTimestamp, Request $request, Response $response, Document $user, Database $dbForProject, Event $queueForEvents) {
$user->setAttribute('status', false);
@@ -2974,14 +2947,14 @@ Http::patch('/v1/account/status')
$protocol = $request->getProtocol();
$response
- ->addCookie($authentication->getCookieName() . '_legacy', '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null)
- ->addCookie($authentication->getCookieName(), '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite'))
+ ->addCookie(Auth::$cookieName . '_legacy', '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null)
+ ->addCookie(Auth::$cookieName, '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite'))
;
$response->dynamic($user, Response::MODEL_ACCOUNT);
});
-Http::post('/v1/account/recovery')
+App::post('/v1/account/recovery')
->desc('Create password recovery')
->groups(['api', 'account'])
->label('scope', 'sessions.write')
@@ -3008,15 +2981,14 @@ Http::post('/v1/account/recovery')
->inject('locale')
->inject('queueForMails')
->inject('queueForEvents')
- ->inject('authorization')
- ->action(function (string $email, string $url, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Locale $locale, Mail $queueForMails, Event $queueForEvents, Authorization $authorization) {
+ ->action(function (string $email, string $url, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Locale $locale, Mail $queueForMails, Event $queueForEvents) {
if (empty(System::getEnv('_APP_SMTP_HOST'))) {
throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP Disabled');
}
$url = htmlentities($url);
- $roles = $authorization->getRoles();
+ $roles = Authorization::getRoles();
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
$isAppUser = Auth::isAppUser($roles);
@@ -3050,7 +3022,7 @@ Http::post('/v1/account/recovery')
'ip' => $request->getIP(),
]);
- $authorization->addRole(Role::user($profile->getId())->toString());
+ Authorization::setRole(Role::user($profile->getId())->toString());
$recovery = $dbForProject->createDocument('tokens', $recovery
->setAttribute('$permissions', [
@@ -3072,7 +3044,7 @@ Http::post('/v1/account/recovery')
$message = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-inner-base.tpl');
$message
- ->setParam('{{body}}', $body, escape: false)
+ ->setParam('{{body}}', $body, escapeHtml: false)
->setParam('{{hello}}', $locale->getText("emails.recovery.hello"))
->setParam('{{footer}}', $locale->getText("emails.recovery.footer"))
->setParam('{{thanks}}', $locale->getText("emails.recovery.thanks"))
@@ -3162,7 +3134,7 @@ Http::post('/v1/account/recovery')
->dynamic($recovery, Response::MODEL_TOKEN);
});
-Http::put('/v1/account/recovery')
+App::put('/v1/account/recovery')
->desc('Create password recovery (confirmation)')
->groups(['api', 'account'])
->label('scope', 'sessions.write')
@@ -3188,8 +3160,7 @@ Http::put('/v1/account/recovery')
->inject('project')
->inject('queueForEvents')
->inject('hooks')
- ->inject('authorization')
- ->action(function (string $userId, string $secret, string $password, Response $response, Document $user, Database $dbForProject, Document $project, Event $queueForEvents, Hooks $hooks, Authorization $authorization) {
+ ->action(function (string $userId, string $secret, string $password, Response $response, Document $user, Database $dbForProject, Document $project, Event $queueForEvents, Hooks $hooks) {
$profile = $dbForProject->getDocument('users', $userId);
if ($profile->isEmpty()) {
@@ -3203,7 +3174,7 @@ Http::put('/v1/account/recovery')
throw new Exception(Exception::USER_INVALID_TOKEN);
}
- $authorization->addRole(Role::user($profile->getId())->toString());
+ Authorization::setRole(Role::user($profile->getId())->toString());
$newPassword = Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS);
@@ -3248,7 +3219,7 @@ Http::put('/v1/account/recovery')
$response->dynamic($recoveryDocument, Response::MODEL_TOKEN);
});
-Http::post('/v1/account/verification')
+App::post('/v1/account/verification')
->desc('Create email verification')
->groups(['api', 'account'])
->label('scope', 'account')
@@ -3273,8 +3244,7 @@ Http::post('/v1/account/verification')
->inject('locale')
->inject('queueForEvents')
->inject('queueForMails')
- ->inject('authorization')
- ->action(function (string $url, Request $request, Response $response, Document $project, Document $user, Database $dbForProject, Locale $locale, Event $queueForEvents, Mail $queueForMails, Authorization $authorization) {
+ ->action(function (string $url, Request $request, Response $response, Document $project, Document $user, Database $dbForProject, Locale $locale, Event $queueForEvents, Mail $queueForMails) {
if (empty(System::getEnv('_APP_SMTP_HOST'))) {
throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP Disabled');
@@ -3285,7 +3255,7 @@ Http::post('/v1/account/verification')
throw new Exception(Exception::USER_EMAIL_ALREADY_VERIFIED);
}
- $roles = $authorization->getRoles();
+ $roles = Authorization::getRoles();
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
$isAppUser = Auth::isAppUser($roles);
$verificationSecret = Auth::tokenGenerator(Auth::TOKEN_LENGTH_VERIFICATION);
@@ -3302,7 +3272,7 @@ Http::post('/v1/account/verification')
'ip' => $request->getIP(),
]);
- $authorization->addRole(Role::user($user->getId())->toString());
+ Authorization::setRole(Role::user($user->getId())->toString());
$verification = $dbForProject->createDocument('tokens', $verification
->setAttribute('$permissions', [
@@ -3324,7 +3294,7 @@ Http::post('/v1/account/verification')
$message = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-inner-base.tpl');
$message
- ->setParam('{{body}}', $body, escape: false)
+ ->setParam('{{body}}', $body, escapeHtml: false)
->setParam('{{hello}}', $locale->getText("emails.verification.hello"))
->setParam('{{footer}}', $locale->getText("emails.verification.footer"))
->setParam('{{thanks}}', $locale->getText("emails.verification.thanks"))
@@ -3414,7 +3384,7 @@ Http::post('/v1/account/verification')
->dynamic($verification, Response::MODEL_TOKEN);
});
-Http::put('/v1/account/verification')
+App::put('/v1/account/verification')
->desc('Create email verification (confirmation)')
->groups(['api', 'account'])
->label('scope', 'public')
@@ -3436,10 +3406,9 @@ Http::put('/v1/account/verification')
->inject('user')
->inject('dbForProject')
->inject('queueForEvents')
- ->inject('authorization')
- ->action(function (string $userId, string $secret, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Authorization $authorization) {
+ ->action(function (string $userId, string $secret, Response $response, Document $user, Database $dbForProject, Event $queueForEvents) {
- $profile = $authorization->skip(fn () => $dbForProject->getDocument('users', $userId));
+ $profile = Authorization::skip(fn () => $dbForProject->getDocument('users', $userId));
if ($profile->isEmpty()) {
throw new Exception(Exception::USER_NOT_FOUND);
@@ -3452,7 +3421,7 @@ Http::put('/v1/account/verification')
throw new Exception(Exception::USER_INVALID_TOKEN);
}
- $authorization->addRole(Role::user($profile->getId())->toString());
+ Authorization::setRole(Role::user($profile->getId())->toString());
$profile = $dbForProject->updateDocument('users', $profile->getId(), $profile->setAttribute('emailVerification', true));
@@ -3474,7 +3443,7 @@ Http::put('/v1/account/verification')
$response->dynamic($verification, Response::MODEL_TOKEN);
});
-Http::post('/v1/account/verification/phone')
+App::post('/v1/account/verification/phone')
->desc('Create phone verification')
->groups(['api', 'account', 'auth'])
->label('scope', 'account')
@@ -3499,8 +3468,7 @@ Http::post('/v1/account/verification/phone')
->inject('queueForMessaging')
->inject('project')
->inject('locale')
- ->inject('authorization')
- ->action(function (Request $request, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Messaging $queueForMessaging, Document $project, Locale $locale, Authorization $authorization) {
+ ->action(function (Request $request, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Messaging $queueForMessaging, Document $project, Locale $locale) {
if (empty(System::getEnv('_APP_SMS_PROVIDER'))) {
throw new Exception(Exception::GENERAL_PHONE_DISABLED, 'Phone provider not configured');
}
@@ -3514,7 +3482,7 @@ Http::post('/v1/account/verification/phone')
throw new Exception(Exception::USER_PHONE_ALREADY_VERIFIED);
}
- $roles = $authorization->getRoles();
+ $roles = Authorization::getRoles();
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
$isAppUser = Auth::isAppUser($roles);
@@ -3543,7 +3511,7 @@ Http::post('/v1/account/verification/phone')
'ip' => $request->getIP(),
]);
- $authorization->addRole(Role::user($user->getId())->toString());
+ Authorization::setRole(Role::user($user->getId())->toString());
$verification = $dbForProject->createDocument('tokens', $verification
->setAttribute('$permissions', [
@@ -3603,7 +3571,7 @@ Http::post('/v1/account/verification/phone')
->dynamic($verification, Response::MODEL_TOKEN);
});
-Http::put('/v1/account/verification/phone')
+App::put('/v1/account/verification/phone')
->desc('Update phone verification (confirmation)')
->groups(['api', 'account'])
->label('scope', 'public')
@@ -3625,10 +3593,9 @@ Http::put('/v1/account/verification/phone')
->inject('user')
->inject('dbForProject')
->inject('queueForEvents')
- ->inject('authorization')
- ->action(function (string $userId, string $secret, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Authorization $authorization) {
+ ->action(function (string $userId, string $secret, Response $response, Document $user, Database $dbForProject, Event $queueForEvents) {
- $profile = $authorization->skip(fn () => $dbForProject->getDocument('users', $userId));
+ $profile = Authorization::skip(fn () => $dbForProject->getDocument('users', $userId));
if ($profile->isEmpty()) {
throw new Exception(Exception::USER_NOT_FOUND);
@@ -3640,7 +3607,7 @@ Http::put('/v1/account/verification/phone')
throw new Exception(Exception::USER_INVALID_TOKEN);
}
- $authorization->addRole(Role::user($profile->getId())->toString());
+ Authorization::setRole(Role::user($profile->getId())->toString());
$profile = $dbForProject->updateDocument('users', $profile->getId(), $profile->setAttribute('phoneVerification', true));
@@ -3662,7 +3629,7 @@ Http::put('/v1/account/verification/phone')
$response->dynamic($verificationDocument, Response::MODEL_TOKEN);
});
-Http::patch('/v1/account/mfa')
+App::patch('/v1/account/mfa')
->desc('Update MFA')
->groups(['api', 'account'])
->label('event', 'users.[userId].update.mfa')
@@ -3715,8 +3682,8 @@ Http::patch('/v1/account/mfa')
$response->dynamic($user, Response::MODEL_ACCOUNT);
});
-Http::get('/v1/account/mfa/factors')
- ->desc('List Factors')
+App::get('/v1/account/mfa/factors')
+ ->desc('List factors')
->groups(['api', 'account', 'mfa'])
->label('scope', 'account')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
@@ -3747,8 +3714,8 @@ Http::get('/v1/account/mfa/factors')
$response->dynamic($factors, Response::MODEL_MFA_FACTORS);
});
-Http::post('/v1/account/mfa/authenticators/:type')
- ->desc('Create Authenticator')
+App::post('/v1/account/mfa/authenticators/:type')
+ ->desc('Create authenticator')
->groups(['api', 'account'])
->label('event', 'users.[userId].update.mfa')
->label('scope', 'account')
@@ -3819,8 +3786,8 @@ Http::post('/v1/account/mfa/authenticators/:type')
$response->dynamic($model, Response::MODEL_MFA_TYPE);
});
-Http::put('/v1/account/mfa/authenticators/:type')
- ->desc('Verify Authenticator')
+App::put('/v1/account/mfa/authenticators/:type')
+ ->desc('Verify authenticator')
->groups(['api', 'account'])
->label('event', 'users.[userId].update.mfa')
->label('scope', 'account')
@@ -3884,8 +3851,8 @@ Http::put('/v1/account/mfa/authenticators/:type')
$response->dynamic($user, Response::MODEL_ACCOUNT);
});
-Http::post('/v1/account/mfa/recovery-codes')
- ->desc('Create MFA Recovery Codes')
+App::post('/v1/account/mfa/recovery-codes')
+ ->desc('Create MFA recovery codes')
->groups(['api', 'account'])
->label('event', 'users.[userId].update.mfa')
->label('scope', 'account')
@@ -3926,8 +3893,8 @@ Http::post('/v1/account/mfa/recovery-codes')
$response->dynamic($document, Response::MODEL_MFA_RECOVERY_CODES);
});
-Http::patch('/v1/account/mfa/recovery-codes')
- ->desc('Regenerate MFA Recovery Codes')
+App::patch('/v1/account/mfa/recovery-codes')
+ ->desc('Regenerate MFA recovery codes')
->groups(['api', 'account', 'mfaProtected'])
->label('event', 'users.[userId].update.mfa')
->label('scope', 'account')
@@ -3967,8 +3934,8 @@ Http::patch('/v1/account/mfa/recovery-codes')
$response->dynamic($document, Response::MODEL_MFA_RECOVERY_CODES);
});
-Http::get('/v1/account/mfa/recovery-codes')
- ->desc('Get MFA Recovery Codes')
+App::get('/v1/account/mfa/recovery-codes')
+ ->desc('Get MFA recovery codes')
->groups(['api', 'account', 'mfaProtected'])
->label('scope', 'account')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
@@ -3997,8 +3964,8 @@ Http::get('/v1/account/mfa/recovery-codes')
$response->dynamic($document, Response::MODEL_MFA_RECOVERY_CODES);
});
-Http::delete('/v1/account/mfa/authenticators/:type')
- ->desc('Delete Authenticator')
+App::delete('/v1/account/mfa/authenticators/:type')
+ ->desc('Delete authenticator')
->groups(['api', 'account', 'mfaProtected'])
->label('event', 'users.[userId].delete.mfa')
->label('scope', 'account')
@@ -4035,8 +4002,8 @@ Http::delete('/v1/account/mfa/authenticators/:type')
$response->noContent();
});
-Http::post('/v1/account/mfa/challenge')
- ->desc('Create MFA Challenge')
+App::post('/v1/account/mfa/challenge')
+ ->desc('Create MFA challenge')
->groups(['api', 'account', 'mfa'])
->label('scope', 'account')
->label('event', 'users.[userId].challenges.[challengeId].create')
@@ -4223,8 +4190,8 @@ Http::post('/v1/account/mfa/challenge')
$response->dynamic($challenge, Response::MODEL_MFA_CHALLENGE);
});
-Http::put('/v1/account/mfa/challenge')
- ->desc('Create MFA Challenge (confirmation)')
+App::put('/v1/account/mfa/challenge')
+ ->desc('Create MFA challenge (confirmation)')
->groups(['api', 'account', 'mfa'])
->label('scope', 'account')
->label('event', 'users.[userId].sessions.[sessionId].create')
@@ -4310,7 +4277,7 @@ Http::put('/v1/account/mfa/challenge')
$response->dynamic($session, Response::MODEL_SESSION);
});
-Http::post('/v1/account/targets/push')
+App::post('/v1/account/targets/push')
->desc('Create push target')
->groups(['api', 'account'])
->label('scope', 'targets.write')
@@ -4331,14 +4298,12 @@ Http::post('/v1/account/targets/push')
->inject('request')
->inject('response')
->inject('dbForProject')
- ->inject('authorization')
- ->inject('authentication')
- ->action(function (string $targetId, string $identifier, string $providerId, Event $queueForEvents, Document $user, Request $request, Response $response, Database $dbForProject, Authorization $authorization, Authentication $authentication) {
+ ->action(function (string $targetId, string $identifier, string $providerId, Event $queueForEvents, Document $user, Request $request, Response $response, Database $dbForProject) {
$targetId = $targetId == 'unique()' ? ID::unique() : $targetId;
- $provider = $authorization->skip(fn () => $dbForProject->getDocument('providers', $providerId));
+ $provider = Authorization::skip(fn () => $dbForProject->getDocument('providers', $providerId));
- $target = $authorization->skip(fn () => $dbForProject->getDocument('targets', $targetId));
+ $target = Authorization::skip(fn () => $dbForProject->getDocument('targets', $targetId));
if (!$target->isEmpty()) {
throw new Exception(Exception::USER_TARGET_ALREADY_EXISTS);
@@ -4349,7 +4314,7 @@ Http::post('/v1/account/targets/push')
$device = $detector->getDevice();
- $sessionId = Auth::sessionVerify($user->getAttribute('sessions'), $authentication->getSecret());
+ $sessionId = Auth::sessionVerify($user->getAttribute('sessions'), Auth::$secret);
$session = $dbForProject->getDocument('sessions', $sessionId);
try {
@@ -4385,7 +4350,7 @@ Http::post('/v1/account/targets/push')
->dynamic($target, Response::MODEL_TARGET);
});
-Http::put('/v1/account/targets/:targetId/push')
+App::put('/v1/account/targets/:targetId/push')
->desc('Update push target')
->groups(['api', 'account'])
->label('scope', 'targets.write')
@@ -4405,10 +4370,9 @@ Http::put('/v1/account/targets/:targetId/push')
->inject('request')
->inject('response')
->inject('dbForProject')
- ->inject('authorization')
- ->action(function (string $targetId, string $identifier, Event $queueForEvents, Document $user, Request $request, Response $response, Database $dbForProject, Authorization $authorization) {
+ ->action(function (string $targetId, string $identifier, Event $queueForEvents, Document $user, Request $request, Response $response, Database $dbForProject) {
- $target = $authorization->skip(fn () => $dbForProject->getDocument('targets', $targetId));
+ $target = Authorization::skip(fn () => $dbForProject->getDocument('targets', $targetId));
if ($target->isEmpty()) {
throw new Exception(Exception::USER_TARGET_NOT_FOUND);
@@ -4441,7 +4405,7 @@ Http::put('/v1/account/targets/:targetId/push')
->dynamic($target, Response::MODEL_TARGET);
});
-Http::delete('/v1/account/targets/:targetId/push')
+App::delete('/v1/account/targets/:targetId/push')
->desc('Delete push target')
->groups(['api', 'account'])
->label('scope', 'targets.write')
@@ -4461,9 +4425,8 @@ Http::delete('/v1/account/targets/:targetId/push')
->inject('request')
->inject('response')
->inject('dbForProject')
- ->inject('authorization')
- ->action(function (string $targetId, Event $queueForEvents, Delete $queueForDeletes, Document $user, Request $request, Response $response, Database $dbForProject, Authorization $authorization) {
- $target = $authorization->skip(fn () => $dbForProject->getDocument('targets', $targetId));
+ ->action(function (string $targetId, Event $queueForEvents, Delete $queueForDeletes, Document $user, Request $request, Response $response, Database $dbForProject) {
+ $target = Authorization::skip(fn () => $dbForProject->getDocument('targets', $targetId));
if ($target->isEmpty()) {
throw new Exception(Exception::USER_TARGET_NOT_FOUND);
@@ -4488,8 +4451,8 @@ Http::delete('/v1/account/targets/:targetId/push')
$response->noContent();
});
-Http::get('/v1/account/identities')
- ->desc('List Identities')
+App::get('/v1/account/identities')
+ ->desc('List identities')
->groups(['api', 'account'])
->label('scope', 'account')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
@@ -4544,7 +4507,7 @@ Http::get('/v1/account/identities')
]), Response::MODEL_IDENTITY_LIST);
});
-Http::delete('/v1/account/identities/:identityId')
+App::delete('/v1/account/identities/:identityId')
->desc('Delete identity')
->groups(['api', 'account'])
->label('scope', 'account')
diff --git a/app/controllers/api/avatars.php b/app/controllers/api/avatars.php
index 54ba096c5d..fcff3e4179 100644
--- a/app/controllers/api/avatars.php
+++ b/app/controllers/api/avatars.php
@@ -5,7 +5,7 @@ use Appwrite\URL\URL as URLParse;
use Appwrite\Utopia\Response;
use chillerlan\QRCode\QRCode;
use chillerlan\QRCode\QROptions;
-use Utopia\CLI\Console;
+use Utopia\App;
use Utopia\Config\Config;
use Utopia\Database\Database;
use Utopia\Database\DateTime;
@@ -14,17 +14,15 @@ use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\UID;
use Utopia\Domains\Domain;
use Utopia\Fetch\Client;
-use Utopia\Http\Http;
-use Utopia\Http\Validator\Boolean;
-use Utopia\Http\Validator\HexColor;
-use Utopia\Http\Validator\Range;
-use Utopia\Http\Validator\Text;
-use Utopia\Http\Validator\URL;
-use Utopia\Http\Validator\WhiteList;
use Utopia\Image\Image;
-use Utopia\Logger\Log;
use Utopia\Logger\Logger;
use Utopia\System\System;
+use Utopia\Validator\Boolean;
+use Utopia\Validator\HexColor;
+use Utopia\Validator\Range;
+use Utopia\Validator\Text;
+use Utopia\Validator\URL;
+use Utopia\Validator\WhiteList;
$avatarCallback = function (string $type, string $code, int $width, int $height, int $quality, Response $response) {
@@ -63,9 +61,9 @@ $avatarCallback = function (string $type, string $code, int $width, int $height,
unset($image);
};
-$getUserGitHub = function (string $userId, Document $project, Database $dbForProject, Database $dbForConsole, ?Logger $logger, Authorization $auth) {
+$getUserGitHub = function (string $userId, Document $project, Database $dbForProject, Database $dbForConsole, ?Logger $logger) {
try {
- $user = $auth->skip(fn () => $dbForConsole->getDocument('users', $userId));
+ $user = Authorization::skip(fn () => $dbForConsole->getDocument('users', $userId));
$sessions = $user->getAttribute('sessions', []);
@@ -116,7 +114,7 @@ $getUserGitHub = function (string $userId, Document $project, Database $dbForPro
->setAttribute('providerRefreshToken', $refreshToken)
->setAttribute('providerAccessTokenExpiry', DateTime::addSeconds(new \DateTime(), (int)$oauth2->getAccessTokenExpiry('')));
- $auth->skip(fn () => $dbForProject->updateDocument('sessions', $gitHubSession->getId(), $gitHubSession));
+ Authorization::skip(fn () => $dbForProject->updateDocument('sessions', $gitHubSession->getId(), $gitHubSession));
$dbForProject->purgeCachedDocument('users', $user->getId());
} catch (Throwable $err) {
@@ -124,7 +122,7 @@ $getUserGitHub = function (string $userId, Document $project, Database $dbForPro
do {
$previousAccessToken = $gitHubSession->getAttribute('providerAccessToken');
- $user = $auth->skip(fn () => $dbForConsole->getDocument('users', $userId));
+ $user = Authorization::skip(fn () => $dbForConsole->getDocument('users', $userId));
$sessions = $user->getAttribute('sessions', []);
$gitHubSession = new Document();
@@ -156,42 +154,11 @@ $getUserGitHub = function (string $userId, Document $project, Database $dbForPro
'id' => $githubId
];
} catch (Exception $error) {
- if ($logger) {
- $version = System::getEnv('_APP_VERSION', 'UNKNOWN');
-
- $log = new Log();
- $log->setNamespace('console');
- $log->setServer(\gethostname());
- $log->setVersion($version);
- $log->setType(Log::TYPE_ERROR);
- $log->setMessage($error->getMessage());
-
- $log->addTag('code', $error->getCode());
- $log->addTag('verboseType', get_class($error));
-
- $log->addExtra('file', $error->getFile());
- $log->addExtra('line', $error->getLine());
- $log->addExtra('trace', $error->getTraceAsString());
- $log->addExtra('detailedTrace', $error->getTrace());
-
- $log->setAction('avatarsGetGitHub');
-
- $isProduction = System::getEnv('_APP_ENV', 'development') === 'production';
-
- $log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING);
-
- $responseCode = $logger->addLog($log);
- Console::info('GitHub error log pushed with status code: ' . $responseCode);
- }
-
- Console::warning("Failed: {$error->getMessage()}");
- Console::warning($error->getTraceAsString());
-
return [];
}
};
-Http::get('/v1/avatars/credit-cards/:code')
+App::get('/v1/avatars/credit-cards/:code')
->desc('Get credit card icon')
->groups(['api', 'avatars'])
->label('scope', 'avatars.read')
@@ -211,7 +178,7 @@ Http::get('/v1/avatars/credit-cards/:code')
->inject('response')
->action(fn (string $code, int $width, int $height, int $quality, Response $response) => $avatarCallback('credit-cards', $code, $width, $height, $quality, $response));
-Http::get('/v1/avatars/browsers/:code')
+App::get('/v1/avatars/browsers/:code')
->desc('Get browser icon')
->groups(['api', 'avatars'])
->label('scope', 'avatars.read')
@@ -231,7 +198,7 @@ Http::get('/v1/avatars/browsers/:code')
->inject('response')
->action(fn (string $code, int $width, int $height, int $quality, Response $response) => $avatarCallback('browsers', $code, $width, $height, $quality, $response));
-Http::get('/v1/avatars/flags/:code')
+App::get('/v1/avatars/flags/:code')
->desc('Get country flag')
->groups(['api', 'avatars'])
->label('scope', 'avatars.read')
@@ -251,7 +218,7 @@ Http::get('/v1/avatars/flags/:code')
->inject('response')
->action(fn (string $code, int $width, int $height, int $quality, Response $response) => $avatarCallback('flags', $code, $width, $height, $quality, $response));
-Http::get('/v1/avatars/image')
+App::get('/v1/avatars/image')
->desc('Get image from URL')
->groups(['api', 'avatars'])
->label('scope', 'avatars.read')
@@ -314,7 +281,7 @@ Http::get('/v1/avatars/image')
unset($image);
});
-Http::get('/v1/avatars/favicon')
+App::get('/v1/avatars/favicon')
->desc('Get favicon')
->groups(['api', 'avatars'])
->label('scope', 'avatars.read')
@@ -459,7 +426,7 @@ Http::get('/v1/avatars/favicon')
unset($image);
});
-Http::get('/v1/avatars/qr')
+App::get('/v1/avatars/qr')
->desc('Get QR code')
->groups(['api', 'avatars'])
->label('scope', 'avatars.read')
@@ -499,7 +466,7 @@ Http::get('/v1/avatars/qr')
->send($image->output('png', 9));
});
-Http::get('/v1/avatars/initials')
+App::get('/v1/avatars/initials')
->desc('Get user initials')
->groups(['api', 'avatars'])
->label('scope', 'avatars.read')
@@ -582,8 +549,8 @@ Http::get('/v1/avatars/initials')
->file($image->getImageBlob());
});
-Http::get('/v1/cards/cloud')
- ->desc('Get Front Of Cloud Card')
+App::get('/v1/cards/cloud')
+ ->desc('Get front Of Cloud Card')
->groups(['api', 'avatars'])
->label('scope', 'avatars.read')
->label('cache', true)
@@ -604,9 +571,8 @@ Http::get('/v1/cards/cloud')
->inject('contributors')
->inject('employees')
->inject('logger')
- ->inject('auth')
- ->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForConsole, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger, Authorization $auth) use ($getUserGitHub) {
- $user = $auth->skip(fn () => $dbForConsole->getDocument('users', $userId));
+ ->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForConsole, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger) use ($getUserGitHub) {
+ $user = Authorization::skip(fn () => $dbForConsole->getDocument('users', $userId));
if ($user->isEmpty() && empty($mock)) {
throw new Exception(Exception::USER_NOT_FOUND);
@@ -617,7 +583,7 @@ Http::get('/v1/cards/cloud')
$email = $user->getAttribute('email', '');
$createdAt = new \DateTime($user->getCreatedAt());
- $gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForConsole, $logger, $auth);
+ $gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForConsole, $logger);
$githubName = $gitHub['name'] ?? '';
$githubId = $gitHub['id'] ?? '';
@@ -790,8 +756,8 @@ Http::get('/v1/cards/cloud')
->file($baseImage->getImageBlob());
});
-Http::get('/v1/cards/cloud-back')
- ->desc('Get Back Of Cloud Card')
+App::get('/v1/cards/cloud-back')
+ ->desc('Get back Of Cloud Card')
->groups(['api', 'avatars'])
->label('scope', 'avatars.read')
->label('cache', true)
@@ -812,9 +778,8 @@ Http::get('/v1/cards/cloud-back')
->inject('contributors')
->inject('employees')
->inject('logger')
- ->inject('auth')
- ->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForConsole, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger, Authorization $auth) use ($getUserGitHub) {
- $user = $auth->skip(fn () => $dbForConsole->getDocument('users', $userId));
+ ->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForConsole, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger) use ($getUserGitHub) {
+ $user = Authorization::skip(fn () => $dbForConsole->getDocument('users', $userId));
if ($user->isEmpty() && empty($mock)) {
throw new Exception(Exception::USER_NOT_FOUND);
@@ -824,7 +789,7 @@ Http::get('/v1/cards/cloud-back')
$userId = $user->getId();
$email = $user->getAttribute('email', '');
- $gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForConsole, $logger, $auth);
+ $gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForConsole, $logger);
$githubId = $gitHub['id'] ?? '';
$isHero = \array_key_exists($email, $heroes);
@@ -869,8 +834,8 @@ Http::get('/v1/cards/cloud-back')
->file($baseImage->getImageBlob());
});
-Http::get('/v1/cards/cloud-og')
- ->desc('Get OG Image From Cloud Card')
+App::get('/v1/cards/cloud-og')
+ ->desc('Get OG image From Cloud Card')
->groups(['api', 'avatars'])
->label('scope', 'avatars.read')
->label('cache', true)
@@ -891,9 +856,8 @@ Http::get('/v1/cards/cloud-og')
->inject('contributors')
->inject('employees')
->inject('logger')
- ->inject('auth')
- ->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForConsole, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger, Authorization $auth) use ($getUserGitHub) {
- $user = $auth->skip(fn () => $dbForConsole->getDocument('users', $userId));
+ ->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForConsole, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger) use ($getUserGitHub) {
+ $user = Authorization::skip(fn () => $dbForConsole->getDocument('users', $userId));
if ($user->isEmpty() && empty($mock)) {
throw new Exception(Exception::USER_NOT_FOUND);
@@ -908,7 +872,7 @@ Http::get('/v1/cards/cloud-og')
$email = $user->getAttribute('email', '');
$createdAt = new \DateTime($user->getCreatedAt());
- $gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForConsole, $logger, $auth);
+ $gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForConsole, $logger);
$githubName = $gitHub['name'] ?? '';
$githubId = $gitHub['id'] ?? '';
diff --git a/app/controllers/api/console.php b/app/controllers/api/console.php
index 2a393497ae..eeb823a3d3 100644
--- a/app/controllers/api/console.php
+++ b/app/controllers/api/console.php
@@ -2,12 +2,12 @@
use Appwrite\Extend\Exception;
use Appwrite\Utopia\Response;
+use Utopia\App;
use Utopia\Database\Document;
-use Utopia\Http\Http;
-use Utopia\Http\Validator\Text;
use Utopia\System\System;
+use Utopia\Validator\Text;
-Http::init()
+App::init()
->groups(['console'])
->inject('project')
->action(function (Document $project) {
@@ -17,7 +17,7 @@ Http::init()
});
-Http::get('/v1/console/variables')
+App::get('/v1/console/variables')
->desc('Get variables')
->groups(['api', 'projects'])
->label('scope', 'projects.read')
@@ -56,8 +56,8 @@ Http::get('/v1/console/variables')
$response->dynamic($variables, Response::MODEL_CONSOLE_VARIABLES);
});
-Http::post('/v1/console/assistant')
- ->desc('Ask Query')
+App::post('/v1/console/assistant')
+ ->desc('Ask query')
->groups(['api', 'assistant'])
->label('scope', 'assistant.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
diff --git a/app/controllers/api/databases.php b/app/controllers/api/databases.php
index be22b66cb8..9c110647af 100644
--- a/app/controllers/api/databases.php
+++ b/app/controllers/api/databases.php
@@ -5,6 +5,7 @@ use Appwrite\Detector\Detector;
use Appwrite\Event\Database as EventDatabase;
use Appwrite\Event\Delete;
use Appwrite\Event\Event;
+use Appwrite\Event\Usage;
use Appwrite\Extend\Exception;
use Appwrite\Network\Validator\Email;
use Appwrite\Utopia\Database\Validator\CustomId;
@@ -15,6 +16,7 @@ use Appwrite\Utopia\Database\Validator\Queries\Indexes;
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response;
use MaxMind\Db\Reader;
+use Utopia\App;
use Utopia\Audit\Audit;
use Utopia\Config\Config;
use Utopia\Database\Database;
@@ -33,7 +35,6 @@ use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
-use Utopia\Database\Validator\Authorization\Input;
use Utopia\Database\Validator\Datetime as DatetimeValidator;
use Utopia\Database\Validator\Index as IndexValidator;
use Utopia\Database\Validator\Key;
@@ -43,19 +44,18 @@ use Utopia\Database\Validator\Query\Limit;
use Utopia\Database\Validator\Query\Offset;
use Utopia\Database\Validator\Structure;
use Utopia\Database\Validator\UID;
-use Utopia\Http\Http;
-use Utopia\Http\Validator\ArrayList;
-use Utopia\Http\Validator\Boolean;
-use Utopia\Http\Validator\FloatValidator;
-use Utopia\Http\Validator\Integer;
-use Utopia\Http\Validator\IP;
-use Utopia\Http\Validator\JSON;
-use Utopia\Http\Validator\Nullable;
-use Utopia\Http\Validator\Range;
-use Utopia\Http\Validator\Text;
-use Utopia\Http\Validator\URL;
-use Utopia\Http\Validator\WhiteList;
use Utopia\Locale\Locale;
+use Utopia\Validator\ArrayList;
+use Utopia\Validator\Boolean;
+use Utopia\Validator\FloatValidator;
+use Utopia\Validator\Integer;
+use Utopia\Validator\IP;
+use Utopia\Validator\JSON;
+use Utopia\Validator\Nullable;
+use Utopia\Validator\Range;
+use Utopia\Validator\Text;
+use Utopia\Validator\URL;
+use Utopia\Validator\WhiteList;
/**
* * Create attribute of varying type
@@ -77,7 +77,7 @@ use Utopia\Locale\Locale;
* @throws ConflictException
* @throws Exception
*/
-function createAttribute(string $databaseId, string $collectionId, Document $attribute, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Authorization $authorization): Document
+function createAttribute(string $databaseId, string $collectionId, Document $attribute, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents): Document
{
$key = $attribute->getAttribute('key');
$type = $attribute->getAttribute('type', '');
@@ -91,7 +91,7 @@ function createAttribute(string $databaseId, string $collectionId, Document $att
$default = $attribute->getAttribute('default');
$options = $attribute->getAttribute('options', []);
- $db = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId));
+ $db = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
if ($db->isEmpty()) {
throw new Exception(Exception::DATABASE_NOT_FOUND);
@@ -226,7 +226,6 @@ function createAttribute(string $databaseId, string $collectionId, Document $att
}
function updateAttribute(
- Authorization $authorization,
string $databaseId,
string $collectionId,
string $key,
@@ -240,10 +239,10 @@ function updateAttribute(
int|float $min = null,
int|float $max = null,
array $elements = null,
- string $newKey = null,
array $options = [],
+ string $newKey = null,
): Document {
- $db = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId));
+ $db = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
if ($db->isEmpty()) {
throw new Exception(Exception::DATABASE_NOT_FOUND);
@@ -422,19 +421,19 @@ function updateAttribute(
return $attribute;
}
-Http::init()
+App::init()
->groups(['api', 'database'])
->inject('request')
->inject('dbForProject')
->action(function (Request $request, Database $dbForProject) {
$timeout = \intval($request->getHeader('x-appwrite-timeout'));
- if (!empty($timeout) && Http::isDevelopment()) {
+ if (!empty($timeout) && App::isDevelopment()) {
$dbForProject->setTimeout($timeout);
}
});
-Http::post('/v1/databases')
+App::post('/v1/databases')
->desc('Create database')
->groups(['api', 'database'])
->label('event', 'databases.[databaseId].create')
@@ -454,7 +453,8 @@ Http::post('/v1/databases')
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
- ->action(function (string $databaseId, string $name, bool $enabled, Response $response, Database $dbForProject, Event $queueForEvents) {
+ ->inject('queueForUsage')
+ ->action(function (string $databaseId, string $name, bool $enabled, Response $response, Database $dbForProject, Event $queueForEvents, Usage $queueForUsage) {
$databaseId = $databaseId == 'unique()' ? ID::unique() : $databaseId;
@@ -504,13 +504,14 @@ Http::post('/v1/databases')
}
$queueForEvents->setParam('databaseId', $database->getId());
+ $queueForUsage->addMetric(str_replace(['{databaseInternalId}'], [$database->getInternalId()], METRIC_DATABASE_ID_STORAGE), 1); // per database
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($database, Response::MODEL_DATABASE);
});
-Http::get('/v1/databases')
+App::get('/v1/databases')
->desc('List databases')
->groups(['api', 'database'])
->label('scope', 'databases.read')
@@ -563,7 +564,7 @@ Http::get('/v1/databases')
]), Response::MODEL_DATABASE_LIST);
});
-Http::get('/v1/databases/:databaseId')
+App::get('/v1/databases/:databaseId')
->desc('Get database')
->groups(['api', 'database'])
->label('scope', 'databases.read')
@@ -588,7 +589,7 @@ Http::get('/v1/databases/:databaseId')
$response->dynamic($database, Response::MODEL_DATABASE);
});
-Http::get('/v1/databases/:databaseId/logs')
+App::get('/v1/databases/:databaseId/logs')
->desc('List database logs')
->groups(['api', 'database'])
->label('scope', 'databases.read')
@@ -605,8 +606,7 @@ Http::get('/v1/databases/:databaseId/logs')
->inject('dbForProject')
->inject('locale')
->inject('geodb')
- ->inject('authorization')
- ->action(function (string $databaseId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb, Authorization $authorization) {
+ ->action(function (string $databaseId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) {
$database = $dbForProject->getDocument('databases', $databaseId);
@@ -680,7 +680,7 @@ Http::get('/v1/databases/:databaseId/logs')
});
-Http::put('/v1/databases/:databaseId')
+App::put('/v1/databases/:databaseId')
->desc('Update database')
->groups(['api', 'database', 'schema'])
->label('scope', 'databases.write')
@@ -718,7 +718,7 @@ Http::put('/v1/databases/:databaseId')
$response->dynamic($database, Response::MODEL_DATABASE);
});
-Http::delete('/v1/databases/:databaseId')
+App::delete('/v1/databases/:databaseId')
->desc('Delete database')
->groups(['api', 'database', 'schema'])
->label('scope', 'databases.write')
@@ -736,7 +736,8 @@ Http::delete('/v1/databases/:databaseId')
->inject('dbForProject')
->inject('queueForDatabase')
->inject('queueForEvents')
- ->action(function (string $databaseId, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents) {
+ ->inject('queueForUsage')
+ ->action(function (string $databaseId, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Usage $queueForUsage) {
$database = $dbForProject->getDocument('databases', $databaseId);
@@ -759,10 +760,13 @@ Http::delete('/v1/databases/:databaseId')
->setParam('databaseId', $database->getId())
->setPayload($response->output($database, Response::MODEL_DATABASE));
+ $queueForUsage
+ ->addMetric(METRIC_DATABASES_STORAGE, 1); // Global, deletion forces full recalculation
+
$response->noContent();
});
-Http::post('/v1/databases/:databaseId/collections')
+App::post('/v1/databases/:databaseId/collections')
->desc('Create collection')
->groups(['api', 'database'])
->label('event', 'databases.[databaseId].collections.[collectionId].create')
@@ -786,10 +790,9 @@ Http::post('/v1/databases/:databaseId/collections')
->inject('dbForProject')
->inject('mode')
->inject('queueForEvents')
- ->inject('authorization')
- ->action(function (string $databaseId, string $collectionId, string $name, ?array $permissions, bool $documentSecurity, bool $enabled, Response $response, Database $dbForProject, string $mode, Event $queueForEvents, Authorization $authorization) {
+ ->action(function (string $databaseId, string $collectionId, string $name, ?array $permissions, bool $documentSecurity, bool $enabled, Response $response, Database $dbForProject, string $mode, Event $queueForEvents) {
- $database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId));
+ $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
if ($database->isEmpty()) {
throw new Exception(Exception::DATABASE_NOT_FOUND);
@@ -830,7 +833,7 @@ Http::post('/v1/databases/:databaseId/collections')
->dynamic($collection, Response::MODEL_COLLECTION);
});
-Http::get('/v1/databases/:databaseId/collections')
+App::get('/v1/databases/:databaseId/collections')
->alias('/v1/database/collections', ['databaseId' => 'default'])
->desc('List collections')
->groups(['api', 'database'])
@@ -848,10 +851,9 @@ Http::get('/v1/databases/:databaseId/collections')
->inject('response')
->inject('dbForProject')
->inject('mode')
- ->inject('authorization')
- ->action(function (string $databaseId, array $queries, string $search, Response $response, Database $dbForProject, string $mode, Authorization $authorization) {
+ ->action(function (string $databaseId, array $queries, string $search, Response $response, Database $dbForProject, string $mode) {
- $database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId));
+ $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
if ($database->isEmpty()) {
throw new Exception(Exception::DATABASE_NOT_FOUND);
@@ -894,7 +896,7 @@ Http::get('/v1/databases/:databaseId/collections')
]), Response::MODEL_COLLECTION_LIST);
});
-Http::get('/v1/databases/:databaseId/collections/:collectionId')
+App::get('/v1/databases/:databaseId/collections/:collectionId')
->alias('/v1/database/collections/:collectionId', ['databaseId' => 'default'])
->desc('Get collection')
->groups(['api', 'database'])
@@ -911,10 +913,9 @@ Http::get('/v1/databases/:databaseId/collections/:collectionId')
->inject('response')
->inject('dbForProject')
->inject('mode')
- ->inject('authorization')
- ->action(function (string $databaseId, string $collectionId, Response $response, Database $dbForProject, string $mode, Authorization $authorization) {
+ ->action(function (string $databaseId, string $collectionId, Response $response, Database $dbForProject, string $mode) {
- $database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId));
+ $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
if ($database->isEmpty()) {
throw new Exception(Exception::DATABASE_NOT_FOUND);
@@ -929,7 +930,7 @@ Http::get('/v1/databases/:databaseId/collections/:collectionId')
$response->dynamic($collection, Response::MODEL_COLLECTION);
});
-Http::get('/v1/databases/:databaseId/collections/:collectionId/logs')
+App::get('/v1/databases/:databaseId/collections/:collectionId/logs')
->alias('/v1/database/collections/:collectionId/logs', ['databaseId' => 'default'])
->desc('List collection logs')
->groups(['api', 'database'])
@@ -948,10 +949,9 @@ Http::get('/v1/databases/:databaseId/collections/:collectionId/logs')
->inject('dbForProject')
->inject('locale')
->inject('geodb')
- ->inject('authorization')
- ->action(function (string $databaseId, string $collectionId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb, Authorization $authorization) {
+ ->action(function (string $databaseId, string $collectionId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) {
- $database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId));
+ $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
if ($database->isEmpty()) {
throw new Exception(Exception::DATABASE_NOT_FOUND);
@@ -1030,7 +1030,7 @@ Http::get('/v1/databases/:databaseId/collections/:collectionId/logs')
});
-Http::put('/v1/databases/:databaseId/collections/:collectionId')
+App::put('/v1/databases/:databaseId/collections/:collectionId')
->alias('/v1/database/collections/:collectionId', ['databaseId' => 'default'])
->desc('Update collection')
->groups(['api', 'database', 'schema'])
@@ -1055,10 +1055,9 @@ Http::put('/v1/databases/:databaseId/collections/:collectionId')
->inject('dbForProject')
->inject('mode')
->inject('queueForEvents')
- ->inject('authorization')
- ->action(function (string $databaseId, string $collectionId, string $name, ?array $permissions, bool $documentSecurity, bool $enabled, Response $response, Database $dbForProject, string $mode, Event $queueForEvents, Authorization $authorization) {
+ ->action(function (string $databaseId, string $collectionId, string $name, ?array $permissions, bool $documentSecurity, bool $enabled, Response $response, Database $dbForProject, string $mode, Event $queueForEvents) {
- $database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId));
+ $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
if ($database->isEmpty()) {
throw new Exception(Exception::DATABASE_NOT_FOUND);
@@ -1094,7 +1093,7 @@ Http::put('/v1/databases/:databaseId/collections/:collectionId')
$response->dynamic($collection, Response::MODEL_COLLECTION);
});
-Http::delete('/v1/databases/:databaseId/collections/:collectionId')
+App::delete('/v1/databases/:databaseId/collections/:collectionId')
->alias('/v1/database/collections/:collectionId', ['databaseId' => 'default'])
->desc('Delete collection')
->groups(['api', 'database', 'schema'])
@@ -1115,10 +1114,9 @@ Http::delete('/v1/databases/:databaseId/collections/:collectionId')
->inject('queueForDatabase')
->inject('queueForEvents')
->inject('mode')
- ->inject('authorization')
- ->action(function (string $databaseId, string $collectionId, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, string $mode, Authorization $authorization) {
+ ->action(function (string $databaseId, string $collectionId, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, string $mode) {
- $database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId));
+ $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
if ($database->isEmpty()) {
throw new Exception(Exception::DATABASE_NOT_FOUND);
@@ -1150,7 +1148,7 @@ Http::delete('/v1/databases/:databaseId/collections/:collectionId')
$response->noContent();
});
-Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/string')
+App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/string')
->alias('/v1/database/collections/:collectionId/attributes/string', ['databaseId' => 'default'])
->desc('Create string attribute')
->groups(['api', 'database', 'schema'])
@@ -1177,8 +1175,7 @@ Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/strin
->inject('dbForProject')
->inject('queueForDatabase')
->inject('queueForEvents')
- ->inject('authorization')
- ->action(function (string $databaseId, string $collectionId, string $key, ?int $size, ?bool $required, ?string $default, bool $array, bool $encrypt, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Authorization $authorization) {
+ ->action(function (string $databaseId, string $collectionId, string $key, ?int $size, ?bool $required, ?string $default, bool $array, bool $encrypt, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents) {
// Ensure attribute default is within required size
$validator = new Text($size, 0);
@@ -1200,7 +1197,7 @@ Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/strin
'default' => $default,
'array' => $array,
'filters' => $filters,
- ]), $response, $dbForProject, $queueForDatabase, $queueForEvents, $authorization);
+ ]), $response, $dbForProject, $queueForDatabase, $queueForEvents);
$response
@@ -1208,7 +1205,7 @@ Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/strin
->dynamic($attribute, Response::MODEL_ATTRIBUTE_STRING);
});
-Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/email')
+App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/email')
->alias('/v1/database/collections/:collectionId/attributes/email', ['databaseId' => 'default'])
->desc('Create email attribute')
->groups(['api', 'database', 'schema'])
@@ -1233,8 +1230,7 @@ Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/email
->inject('dbForProject')
->inject('queueForDatabase')
->inject('queueForEvents')
- ->inject('authorization')
- ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, bool $array, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Authorization $authorization) {
+ ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, bool $array, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents) {
$attribute = createAttribute($databaseId, $collectionId, new Document([
'key' => $key,
@@ -1244,14 +1240,14 @@ Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/email
'default' => $default,
'array' => $array,
'format' => APP_DATABASE_ATTRIBUTE_EMAIL,
- ]), $response, $dbForProject, $queueForDatabase, $queueForEvents, $authorization);
+ ]), $response, $dbForProject, $queueForDatabase, $queueForEvents);
$response
->setStatusCode(Response::STATUS_CODE_ACCEPTED)
->dynamic($attribute, Response::MODEL_ATTRIBUTE_EMAIL);
});
-Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/enum')
+App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/enum')
->alias('/v1/database/collections/:collectionId/attributes/enum', ['databaseId' => 'default'])
->desc('Create enum attribute')
->groups(['api', 'database', 'schema'])
@@ -1277,8 +1273,7 @@ Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/enum'
->inject('dbForProject')
->inject('queueForDatabase')
->inject('queueForEvents')
- ->inject('authorization')
- ->action(function (string $databaseId, string $collectionId, string $key, array $elements, ?bool $required, ?string $default, bool $array, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Authorization $authorization) {
+ ->action(function (string $databaseId, string $collectionId, string $key, array $elements, ?bool $required, ?string $default, bool $array, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents) {
if (!is_null($default) && !in_array($default, $elements)) {
throw new Exception(Exception::ATTRIBUTE_VALUE_INVALID, 'Default value not found in elements');
}
@@ -1292,14 +1287,14 @@ Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/enum'
'array' => $array,
'format' => APP_DATABASE_ATTRIBUTE_ENUM,
'formatOptions' => ['elements' => $elements],
- ]), $response, $dbForProject, $queueForDatabase, $queueForEvents, $authorization);
+ ]), $response, $dbForProject, $queueForDatabase, $queueForEvents);
$response
->setStatusCode(Response::STATUS_CODE_ACCEPTED)
->dynamic($attribute, Response::MODEL_ATTRIBUTE_ENUM);
});
-Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/ip')
+App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/ip')
->alias('/v1/database/collections/:collectionId/attributes/ip', ['databaseId' => 'default'])
->desc('Create IP address attribute')
->groups(['api', 'database', 'schema'])
@@ -1324,8 +1319,7 @@ Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/ip')
->inject('dbForProject')
->inject('queueForDatabase')
->inject('queueForEvents')
- ->inject('authorization')
- ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, bool $array, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Authorization $authorization) {
+ ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, bool $array, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents) {
$attribute = createAttribute($databaseId, $collectionId, new Document([
'key' => $key,
@@ -1335,14 +1329,14 @@ Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/ip')
'default' => $default,
'array' => $array,
'format' => APP_DATABASE_ATTRIBUTE_IP,
- ]), $response, $dbForProject, $queueForDatabase, $queueForEvents, $authorization);
+ ]), $response, $dbForProject, $queueForDatabase, $queueForEvents);
$response
->setStatusCode(Response::STATUS_CODE_ACCEPTED)
->dynamic($attribute, Response::MODEL_ATTRIBUTE_IP);
});
-Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/url')
+App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/url')
->alias('/v1/database/collections/:collectionId/attributes/url', ['databaseId' => 'default'])
->desc('Create URL attribute')
->groups(['api', 'database', 'schema'])
@@ -1367,8 +1361,7 @@ Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/url')
->inject('dbForProject')
->inject('queueForDatabase')
->inject('queueForEvents')
- ->inject('authorization')
- ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, bool $array, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Authorization $authorization) {
+ ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, bool $array, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents) {
$attribute = createAttribute($databaseId, $collectionId, new Document([
'key' => $key,
@@ -1378,14 +1371,14 @@ Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/url')
'default' => $default,
'array' => $array,
'format' => APP_DATABASE_ATTRIBUTE_URL,
- ]), $response, $dbForProject, $queueForDatabase, $queueForEvents, $authorization);
+ ]), $response, $dbForProject, $queueForDatabase, $queueForEvents);
$response
->setStatusCode(Response::STATUS_CODE_ACCEPTED)
->dynamic($attribute, Response::MODEL_ATTRIBUTE_URL);
});
-Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/integer')
+App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/integer')
->alias('/v1/database/collections/:collectionId/attributes/integer', ['databaseId' => 'default'])
->desc('Create integer attribute')
->groups(['api', 'database', 'schema'])
@@ -1412,8 +1405,7 @@ Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/integ
->inject('dbForProject')
->inject('queueForDatabase')
->inject('queueForEvents')
- ->inject('authorization')
- ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?int $min, ?int $max, ?int $default, bool $array, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Authorization $authorization) {
+ ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?int $min, ?int $max, ?int $default, bool $array, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents) {
// Ensure attribute default is within range
$min = (is_null($min)) ? PHP_INT_MIN : \intval($min);
@@ -1443,7 +1435,7 @@ Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/integ
'min' => $min,
'max' => $max,
],
- ]), $response, $dbForProject, $queueForDatabase, $queueForEvents, $authorization);
+ ]), $response, $dbForProject, $queueForDatabase, $queueForEvents);
$formatOptions = $attribute->getAttribute('formatOptions', []);
@@ -1457,7 +1449,7 @@ Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/integ
->dynamic($attribute, Response::MODEL_ATTRIBUTE_INTEGER);
});
-Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/float')
+App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/float')
->alias('/v1/database/collections/:collectionId/attributes/float', ['databaseId' => 'default'])
->desc('Create float attribute')
->groups(['api', 'database', 'schema'])
@@ -1484,8 +1476,7 @@ Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/float
->inject('dbForProject')
->inject('queueForDatabase')
->inject('queueForEvents')
- ->inject('authorization')
- ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?float $min, ?float $max, ?float $default, bool $array, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Authorization $authorization) {
+ ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?float $min, ?float $max, ?float $default, bool $array, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents) {
// Ensure attribute default is within range
$min = (is_null($min)) ? -PHP_FLOAT_MAX : \floatval($min);
@@ -1518,7 +1509,7 @@ Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/float
'min' => $min,
'max' => $max,
],
- ]), $response, $dbForProject, $queueForDatabase, $queueForEvents, $authorization);
+ ]), $response, $dbForProject, $queueForDatabase, $queueForEvents);
$formatOptions = $attribute->getAttribute('formatOptions', []);
@@ -1532,7 +1523,7 @@ Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/float
->dynamic($attribute, Response::MODEL_ATTRIBUTE_FLOAT);
});
-Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/boolean')
+App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/boolean')
->alias('/v1/database/collections/:collectionId/attributes/boolean', ['databaseId' => 'default'])
->desc('Create boolean attribute')
->groups(['api', 'database', 'schema'])
@@ -1557,8 +1548,7 @@ Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/boole
->inject('dbForProject')
->inject('queueForDatabase')
->inject('queueForEvents')
- ->inject('authorization')
- ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?bool $default, bool $array, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Authorization $authorization) {
+ ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?bool $default, bool $array, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents) {
$attribute = createAttribute($databaseId, $collectionId, new Document([
'key' => $key,
@@ -1567,14 +1557,14 @@ Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/boole
'required' => $required,
'default' => $default,
'array' => $array,
- ]), $response, $dbForProject, $queueForDatabase, $queueForEvents, $authorization);
+ ]), $response, $dbForProject, $queueForDatabase, $queueForEvents);
$response
->setStatusCode(Response::STATUS_CODE_ACCEPTED)
->dynamic($attribute, Response::MODEL_ATTRIBUTE_BOOLEAN);
});
-Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/datetime')
+App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/datetime')
->alias('/v1/database/collections/:collectionId/attributes/datetime', ['databaseId' => 'default'])
->desc('Create datetime attribute')
->groups(['api', 'database'])
@@ -1599,8 +1589,7 @@ Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/datet
->inject('dbForProject')
->inject('queueForDatabase')
->inject('queueForEvents')
- ->inject('authorization')
- ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, bool $array, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Authorization $authorization) {
+ ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, bool $array, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents) {
$filters[] = 'datetime';
@@ -1612,14 +1601,14 @@ Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/datet
'default' => $default,
'array' => $array,
'filters' => $filters,
- ]), $response, $dbForProject, $queueForDatabase, $queueForEvents, $authorization);
+ ]), $response, $dbForProject, $queueForDatabase, $queueForEvents);
$response
->setStatusCode(Response::STATUS_CODE_ACCEPTED)
->dynamic($attribute, Response::MODEL_ATTRIBUTE_DATETIME);
});
-Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/relationship')
+App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/relationship')
->alias('/v1/database/collections/:collectionId/attributes/relationship', ['databaseId' => 'default'])
->desc('Create relationship attribute')
->groups(['api', 'database'])
@@ -1646,7 +1635,6 @@ Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/relat
->inject('dbForProject')
->inject('queueForDatabase')
->inject('queueForEvents')
- ->inject('authorization')
->action(function (
string $databaseId,
string $collectionId,
@@ -1659,13 +1647,12 @@ Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/relat
Response $response,
Database $dbForProject,
EventDatabase $queueForDatabase,
- Event $queueForEvents,
- Authorization $authorization
+ Event $queueForEvents
) {
$key ??= $relatedCollectionId;
$twoWayKey ??= $collectionId;
- $database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId));
+ $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
if ($database->isEmpty()) {
throw new Exception(Exception::DATABASE_NOT_FOUND);
@@ -1735,8 +1722,7 @@ Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/relat
$response,
$dbForProject,
$queueForDatabase,
- $queueForEvents,
- $authorization
+ $queueForEvents
);
$options = $attribute->getAttribute('options', []);
@@ -1750,7 +1736,7 @@ Http::post('/v1/databases/:databaseId/collections/:collectionId/attributes/relat
->dynamic($attribute, Response::MODEL_ATTRIBUTE_RELATIONSHIP);
});
-Http::get('/v1/databases/:databaseId/collections/:collectionId/attributes')
+App::get('/v1/databases/:databaseId/collections/:collectionId/attributes')
->alias('/v1/database/collections/:collectionId/attributes', ['databaseId' => 'default'])
->desc('List attributes')
->groups(['api', 'database'])
@@ -1767,10 +1753,9 @@ Http::get('/v1/databases/:databaseId/collections/:collectionId/attributes')
->param('queries', [], new Attributes(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Attributes::ALLOWED_ATTRIBUTES), true)
->inject('response')
->inject('dbForProject')
- ->inject('authorization')
- ->action(function (string $databaseId, string $collectionId, array $queries, Response $response, Database $dbForProject, Authorization $authorization) {
+ ->action(function (string $databaseId, string $collectionId, array $queries, Response $response, Database $dbForProject) {
/** @var Document $database */
- $database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId));
+ $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
if ($database->isEmpty()) {
throw new Exception(Exception::DATABASE_NOT_FOUND);
@@ -1804,7 +1789,7 @@ Http::get('/v1/databases/:databaseId/collections/:collectionId/attributes')
if ($cursor) {
$attributeId = $cursor->getValue();
- $cursorDocument = $authorization->skip(fn () => $dbForProject->find('attributes', [
+ $cursorDocument = Authorization::skip(fn () => $dbForProject->find('attributes', [
Query::equal('collectionInternalId', [$collection->getInternalId()]),
Query::equal('databaseInternalId', [$database->getInternalId()]),
Query::equal('key', [$attributeId]),
@@ -1829,7 +1814,7 @@ Http::get('/v1/databases/:databaseId/collections/:collectionId/attributes')
]), Response::MODEL_ATTRIBUTE_LIST);
});
-Http::get('/v1/databases/:databaseId/collections/:collectionId/attributes/:key')
+App::get('/v1/databases/:databaseId/collections/:collectionId/attributes/:key')
->alias('/v1/database/collections/:collectionId/attributes/:key', ['databaseId' => 'default'])
->desc('Get attribute')
->groups(['api', 'database'])
@@ -1856,10 +1841,9 @@ Http::get('/v1/databases/:databaseId/collections/:collectionId/attributes/:key')
->param('key', '', new Key(), 'Attribute Key.')
->inject('response')
->inject('dbForProject')
- ->inject('authorization')
- ->action(function (string $databaseId, string $collectionId, string $key, Response $response, Database $dbForProject, Authorization $authorization) {
+ ->action(function (string $databaseId, string $collectionId, string $key, Response $response, Database $dbForProject) {
- $database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId));
+ $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
if ($database->isEmpty()) {
throw new Exception(Exception::DATABASE_NOT_FOUND);
@@ -1905,7 +1889,7 @@ Http::get('/v1/databases/:databaseId/collections/:collectionId/attributes/:key')
$response->dynamic($attribute, $model);
});
-Http::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/string/:key')
+App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/string/:key')
->desc('Update string attribute')
->groups(['api', 'database', 'schema'])
->label('scope', 'collections.write')
@@ -1928,11 +1912,9 @@ Http::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/stri
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
- ->inject('authorization')
- ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, ?int $size, ?string $newKey, Response $response, Database $dbForProject, Event $queueForEvents, Authorization $authorization) {
+ ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, ?int $size, ?string $newKey, Response $response, Database $dbForProject, Event $queueForEvents) {
$attribute = updateAttribute(
- authorization: $authorization,
databaseId: $databaseId,
collectionId: $collectionId,
key: $key,
@@ -1950,7 +1932,7 @@ Http::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/stri
->dynamic($attribute, Response::MODEL_ATTRIBUTE_STRING);
});
-Http::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/email/:key')
+App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/email/:key')
->desc('Update email attribute')
->groups(['api', 'database', 'schema'])
->label('scope', 'collections.write')
@@ -1972,10 +1954,8 @@ Http::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/emai
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
- ->inject('authorization')
- ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, ?string $newKey, Response $response, Database $dbForProject, Event $queueForEvents, Authorization $authorization) {
+ ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, ?string $newKey, Response $response, Database $dbForProject, Event $queueForEvents) {
$attribute = updateAttribute(
- authorization: $authorization,
databaseId: $databaseId,
collectionId: $collectionId,
key: $key,
@@ -1993,7 +1973,7 @@ Http::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/emai
->dynamic($attribute, Response::MODEL_ATTRIBUTE_EMAIL);
});
-Http::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/enum/:key')
+App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/enum/:key')
->desc('Update enum attribute')
->groups(['api', 'database', 'schema'])
->label('scope', 'collections.write')
@@ -2016,10 +1996,8 @@ Http::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/enum
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
- ->inject('authorization')
- ->action(function (string $databaseId, string $collectionId, string $key, ?array $elements, ?bool $required, ?string $default, ?string $newKey, Response $response, Database $dbForProject, Event $queueForEvents, Authorization $authorization) {
+ ->action(function (string $databaseId, string $collectionId, string $key, ?array $elements, ?bool $required, ?string $default, ?string $newKey, Response $response, Database $dbForProject, Event $queueForEvents) {
$attribute = updateAttribute(
- authorization: $authorization,
databaseId: $databaseId,
collectionId: $collectionId,
key: $key,
@@ -2038,7 +2016,7 @@ Http::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/enum
->dynamic($attribute, Response::MODEL_ATTRIBUTE_ENUM);
});
-Http::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/ip/:key')
+App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/ip/:key')
->desc('Update IP address attribute')
->groups(['api', 'database', 'schema'])
->label('scope', 'collections.write')
@@ -2060,10 +2038,8 @@ Http::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/ip/:
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
- ->inject('authorization')
- ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, ?string $newKey, Response $response, Database $dbForProject, Event $queueForEvents, Authorization $authorization) {
+ ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, ?string $newKey, Response $response, Database $dbForProject, Event $queueForEvents) {
$attribute = updateAttribute(
- authorization: $authorization,
databaseId: $databaseId,
collectionId: $collectionId,
key: $key,
@@ -2081,7 +2057,7 @@ Http::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/ip/:
->dynamic($attribute, Response::MODEL_ATTRIBUTE_IP);
});
-Http::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/url/:key')
+App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/url/:key')
->desc('Update URL attribute')
->groups(['api', 'database', 'schema'])
->label('scope', 'collections.write')
@@ -2103,10 +2079,8 @@ Http::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/url/
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
- ->inject('authorization')
- ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, ?string $newKey, Response $response, Database $dbForProject, Event $queueForEvents, Authorization $authorization) {
+ ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, ?string $newKey, Response $response, Database $dbForProject, Event $queueForEvents) {
$attribute = updateAttribute(
- authorization: $authorization,
databaseId: $databaseId,
collectionId: $collectionId,
key: $key,
@@ -2124,7 +2098,7 @@ Http::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/url/
->dynamic($attribute, Response::MODEL_ATTRIBUTE_URL);
});
-Http::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/integer/:key')
+App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/integer/:key')
->desc('Update integer attribute')
->groups(['api', 'database', 'schema'])
->label('scope', 'collections.write')
@@ -2148,10 +2122,8 @@ Http::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/inte
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
- ->inject('authorization')
- ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?int $min, ?int $max, ?int $default, ?string $newKey, Response $response, Database $dbForProject, Event $queueForEvents, Authorization $authorization) {
+ ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?int $min, ?int $max, ?int $default, ?string $newKey, Response $response, Database $dbForProject, Event $queueForEvents) {
$attribute = updateAttribute(
- authorization: $authorization,
databaseId: $databaseId,
collectionId: $collectionId,
key: $key,
@@ -2177,7 +2149,7 @@ Http::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/inte
->dynamic($attribute, Response::MODEL_ATTRIBUTE_INTEGER);
});
-Http::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/float/:key')
+App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/float/:key')
->desc('Update float attribute')
->groups(['api', 'database', 'schema'])
->label('scope', 'collections.write')
@@ -2201,10 +2173,8 @@ Http::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/floa
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
- ->inject('authorization')
- ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?float $min, ?float $max, ?float $default, ?string $newKey, Response $response, Database $dbForProject, Event $queueForEvents, Authorization $authorization) {
+ ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?float $min, ?float $max, ?float $default, ?string $newKey, Response $response, Database $dbForProject, Event $queueForEvents) {
$attribute = updateAttribute(
- authorization: $authorization,
databaseId: $databaseId,
collectionId: $collectionId,
key: $key,
@@ -2230,7 +2200,7 @@ Http::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/floa
->dynamic($attribute, Response::MODEL_ATTRIBUTE_FLOAT);
});
-Http::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/boolean/:key')
+App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/boolean/:key')
->desc('Update boolean attribute')
->groups(['api', 'database', 'schema'])
->label('scope', 'collections.write')
@@ -2252,10 +2222,8 @@ Http::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/bool
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
- ->inject('authorization')
- ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?bool $default, ?string $newKey, Response $response, Database $dbForProject, Event $queueForEvents, Authorization $authorization) {
+ ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?bool $default, ?string $newKey, Response $response, Database $dbForProject, Event $queueForEvents) {
$attribute = updateAttribute(
- authorization: $authorization,
databaseId: $databaseId,
collectionId: $collectionId,
key: $key,
@@ -2272,7 +2240,7 @@ Http::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/bool
->dynamic($attribute, Response::MODEL_ATTRIBUTE_BOOLEAN);
});
-Http::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/datetime/:key')
+App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/datetime/:key')
->desc('Update dateTime attribute')
->groups(['api', 'database', 'schema'])
->label('scope', 'collections.write')
@@ -2294,10 +2262,8 @@ Http::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/date
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
- ->inject('authorization')
- ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, ?string $newKey, Response $response, Database $dbForProject, Event $queueForEvents, Authorization $authorization) {
+ ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, ?string $newKey, Response $response, Database $dbForProject, Event $queueForEvents) {
$attribute = updateAttribute(
- authorization: $authorization,
databaseId: $databaseId,
collectionId: $collectionId,
key: $key,
@@ -2314,7 +2280,7 @@ Http::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/date
->dynamic($attribute, Response::MODEL_ATTRIBUTE_DATETIME);
});
-Http::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/:key/relationship')
+App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/:key/relationship')
->desc('Update relationship attribute')
->groups(['api', 'database', 'schema'])
->label('scope', 'collections.write')
@@ -2335,7 +2301,6 @@ Http::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/:key
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
- ->inject('authorization')
->action(function (
string $databaseId,
string $collectionId,
@@ -2344,11 +2309,9 @@ Http::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/:key
?string $newKey,
Response $response,
Database $dbForProject,
- Event $queueForEvents,
- Authorization $authorization
+ Event $queueForEvents
) {
$attribute = updateAttribute(
- $authorization,
$databaseId,
$collectionId,
$key,
@@ -2373,7 +2336,7 @@ Http::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/:key
->dynamic($attribute, Response::MODEL_ATTRIBUTE_RELATIONSHIP);
});
-Http::delete('/v1/databases/:databaseId/collections/:collectionId/attributes/:key')
+App::delete('/v1/databases/:databaseId/collections/:collectionId/attributes/:key')
->alias('/v1/database/collections/:collectionId/attributes/:key', ['databaseId' => 'default'])
->desc('Delete attribute')
->groups(['api', 'database', 'schema'])
@@ -2394,10 +2357,10 @@ Http::delete('/v1/databases/:databaseId/collections/:collectionId/attributes/:ke
->inject('dbForProject')
->inject('queueForDatabase')
->inject('queueForEvents')
- ->inject('authorization')
- ->action(function (string $databaseId, string $collectionId, string $key, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Authorization $authorization) {
+ ->inject('queueForUsage')
+ ->action(function (string $databaseId, string $collectionId, string $key, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Usage $queueForUsage) {
- $db = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId));
+ $db = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
if ($db->isEmpty()) {
throw new Exception(Exception::DATABASE_NOT_FOUND);
@@ -2480,10 +2443,13 @@ Http::delete('/v1/databases/:databaseId/collections/:collectionId/attributes/:ke
->setContext('database', $db)
->setPayload($response->output($attribute, $model));
+ $queueForUsage
+ ->addMetric(str_replace(['{databaseInternalId}', '{collectionInternalId}'], [$db->getInternalId(), $collection->getInternalId()], METRIC_DATABASE_ID_COLLECTION_ID_STORAGE), 1); // per collection
+
$response->noContent();
});
-Http::post('/v1/databases/:databaseId/collections/:collectionId/indexes')
+App::post('/v1/databases/:databaseId/collections/:collectionId/indexes')
->alias('/v1/database/collections/:collectionId/indexes', ['databaseId' => 'default'])
->desc('Create index')
->groups(['api', 'database'])
@@ -2508,10 +2474,9 @@ Http::post('/v1/databases/:databaseId/collections/:collectionId/indexes')
->inject('dbForProject')
->inject('queueForDatabase')
->inject('queueForEvents')
- ->inject('authorization')
- ->action(function (string $databaseId, string $collectionId, string $key, string $type, array $attributes, array $orders, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Authorization $authorization) {
+ ->action(function (string $databaseId, string $collectionId, string $key, string $type, array $attributes, array $orders, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents) {
- $db = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId));
+ $db = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
if ($db->isEmpty()) {
throw new Exception(Exception::DATABASE_NOT_FOUND);
@@ -2544,7 +2509,7 @@ Http::post('/v1/databases/:databaseId/collections/:collectionId/indexes')
'required' => true,
'array' => false,
'default' => null,
- 'size' => 36
+ 'size' => Database::LENGTH_KEY
];
$oldAttributes[] = [
@@ -2654,7 +2619,7 @@ Http::post('/v1/databases/:databaseId/collections/:collectionId/indexes')
->dynamic($index, Response::MODEL_INDEX);
});
-Http::get('/v1/databases/:databaseId/collections/:collectionId/indexes')
+App::get('/v1/databases/:databaseId/collections/:collectionId/indexes')
->alias('/v1/database/collections/:collectionId/indexes', ['databaseId' => 'default'])
->desc('List indexes')
->groups(['api', 'database'])
@@ -2671,10 +2636,9 @@ Http::get('/v1/databases/:databaseId/collections/:collectionId/indexes')
->param('queries', [], new Indexes(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Indexes::ALLOWED_ATTRIBUTES), true)
->inject('response')
->inject('dbForProject')
- ->inject('authorization')
- ->action(function (string $databaseId, string $collectionId, array $queries, Response $response, Database $dbForProject, Authorization $authorization) {
+ ->action(function (string $databaseId, string $collectionId, array $queries, Response $response, Database $dbForProject) {
/** @var Document $database */
- $database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId));
+ $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
if ($database->isEmpty()) {
throw new Exception(Exception::DATABASE_NOT_FOUND);
@@ -2704,7 +2668,7 @@ Http::get('/v1/databases/:databaseId/collections/:collectionId/indexes')
if ($cursor) {
$indexId = $cursor->getValue();
- $cursorDocument = $authorization->skip(fn () => $dbForProject->find('indexes', [
+ $cursorDocument = Authorization::skip(fn () => $dbForProject->find('indexes', [
Query::equal('collectionInternalId', [$collection->getInternalId()]),
Query::equal('databaseInternalId', [$database->getInternalId()]),
Query::equal('key', [$indexId]),
@@ -2725,7 +2689,7 @@ Http::get('/v1/databases/:databaseId/collections/:collectionId/indexes')
]), Response::MODEL_INDEX_LIST);
});
-Http::get('/v1/databases/:databaseId/collections/:collectionId/indexes/:key')
+App::get('/v1/databases/:databaseId/collections/:collectionId/indexes/:key')
->alias('/v1/database/collections/:collectionId/indexes/:key', ['databaseId' => 'default'])
->desc('Get index')
->groups(['api', 'database'])
@@ -2742,10 +2706,9 @@ Http::get('/v1/databases/:databaseId/collections/:collectionId/indexes/:key')
->param('key', null, new Key(), 'Index Key.')
->inject('response')
->inject('dbForProject')
- ->inject('authorization')
- ->action(function (string $databaseId, string $collectionId, string $key, Response $response, Database $dbForProject, Authorization $authorization) {
+ ->action(function (string $databaseId, string $collectionId, string $key, Response $response, Database $dbForProject) {
- $database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId));
+ $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
if ($database->isEmpty()) {
throw new Exception(Exception::DATABASE_NOT_FOUND);
@@ -2765,7 +2728,7 @@ Http::get('/v1/databases/:databaseId/collections/:collectionId/indexes/:key')
});
-Http::delete('/v1/databases/:databaseId/collections/:collectionId/indexes/:key')
+App::delete('/v1/databases/:databaseId/collections/:collectionId/indexes/:key')
->alias('/v1/database/collections/:collectionId/indexes/:key', ['databaseId' => 'default'])
->desc('Delete index')
->groups(['api', 'database'])
@@ -2786,10 +2749,9 @@ Http::delete('/v1/databases/:databaseId/collections/:collectionId/indexes/:key')
->inject('dbForProject')
->inject('queueForDatabase')
->inject('queueForEvents')
- ->inject('authorization')
- ->action(function (string $databaseId, string $collectionId, string $key, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Authorization $authorization) {
+ ->action(function (string $databaseId, string $collectionId, string $key, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents) {
- $db = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId));
+ $db = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
if ($db->isEmpty()) {
throw new Exception(Exception::DATABASE_NOT_FOUND);
@@ -2830,7 +2792,7 @@ Http::delete('/v1/databases/:databaseId/collections/:collectionId/indexes/:key')
$response->noContent();
});
-Http::post('/v1/databases/:databaseId/collections/:collectionId/documents')
+App::post('/v1/databases/:databaseId/collections/:collectionId/documents')
->alias('/v1/database/collections/:collectionId/documents', ['databaseId' => 'default'])
->desc('Create document')
->groups(['api', 'database'])
@@ -2859,9 +2821,9 @@ Http::post('/v1/databases/:databaseId/collections/:collectionId/documents')
->inject('dbForProject')
->inject('user')
->inject('queueForEvents')
+ ->inject('queueForUsage')
->inject('mode')
- ->inject('authorization')
- ->action(function (string $databaseId, string $documentId, string $collectionId, string|array $data, ?array $permissions, Response $response, Database $dbForProject, Document $user, Event $queueForEvents, string $mode, Authorization $authorization) {
+ ->action(function (string $databaseId, string $documentId, string $collectionId, string|array $data, ?array $permissions, Response $response, Database $dbForProject, Document $user, Event $queueForEvents, Usage $queueForUsage, string $mode) {
$data = (\is_string($data)) ? \json_decode($data, true) : $data; // Cast to JSON array
@@ -2873,16 +2835,16 @@ Http::post('/v1/databases/:databaseId/collections/:collectionId/documents')
throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, '$id is not allowed for creating new documents, try update instead');
}
- $database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId));
+ $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
- $isAPIKey = Auth::isAppUser($authorization->getRoles());
- $isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles());
+ $isAPIKey = Auth::isAppUser(Authorization::getRoles());
+ $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
if ($database->isEmpty() || (!$database->getAttribute('enabled', false) && !$isAPIKey && !$isPrivilegedUser)) {
throw new Exception(Exception::DATABASE_NOT_FOUND);
}
- $collection = $authorization->skip(fn () => $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId));
+ $collection = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId));
if ($collection->isEmpty() || (!$collection->getAttribute('enabled', false) && !$isAPIKey && !$isPrivilegedUser)) {
throw new Exception(Exception::COLLECTION_NOT_FOUND);
@@ -2920,8 +2882,8 @@ Http::post('/v1/databases/:databaseId/collections/:collectionId/documents')
$permission->getIdentifier(),
$permission->getDimension()
))->toString();
- if (!$authorization->isRole($role)) {
- throw new Exception(Exception::USER_UNAUTHORIZED, 'Permissions must be one of: (' . \implode(', ', $authorization->getRoles()) . ')');
+ if (!Authorization::isRole($role)) {
+ throw new Exception(Exception::USER_UNAUTHORIZED, 'Permissions must be one of: (' . \implode(', ', Authorization::getRoles()) . ')');
}
}
}
@@ -2932,16 +2894,17 @@ Http::post('/v1/databases/:databaseId/collections/:collectionId/documents')
$data['$permissions'] = $permissions;
$document = new Document($data);
- $checkPermissions = function (Document $collection, Document $document, string $permission) use (&$checkPermissions, $dbForProject, $database, $authorization) {
+ $checkPermissions = function (Document $collection, Document $document, string $permission) use (&$checkPermissions, $dbForProject, $database) {
$documentSecurity = $collection->getAttribute('documentSecurity', false);
+ $validator = new Authorization($permission);
- $valid = $authorization->isValid(new Input($permission, $collection->getPermissionsByType($permission)));
+ $valid = $validator->isValid($collection->getPermissionsByType($permission));
if (($permission === Database::PERMISSION_UPDATE && !$documentSecurity) || !$valid) {
throw new Exception(Exception::USER_UNAUTHORIZED);
}
if ($permission === Database::PERMISSION_UPDATE) {
- $valid = $valid || $authorization->isValid($document->getUpdate());
+ $valid = $valid || $validator->isValid($document->getUpdate());
if ($documentSecurity && !$valid) {
throw new Exception(Exception::USER_UNAUTHORIZED);
}
@@ -2968,7 +2931,7 @@ Http::post('/v1/databases/:databaseId/collections/:collectionId/documents')
}
$relatedCollectionId = $relationship->getAttribute('relatedCollection');
- $relatedCollection = $authorization->skip(
+ $relatedCollection = Authorization::skip(
fn () => $dbForProject->getDocument('database_' . $database->getInternalId(), $relatedCollectionId)
);
@@ -2982,7 +2945,7 @@ Http::post('/v1/databases/:databaseId/collections/:collectionId/documents')
$relation = new Document($relation);
}
if ($relation instanceof Document) {
- $current = $authorization->skip(
+ $current = Authorization::skip(
fn () => $dbForProject->getDocument('database_' . $database->getInternalId() . '_collection_' . $relatedCollection->getInternalId(), $relation->getId())
);
@@ -3022,7 +2985,7 @@ Http::post('/v1/databases/:databaseId/collections/:collectionId/documents')
}
// Add $collectionId and $databaseId for all documents
- $processDocument = function (Document $collection, Document $document) use (&$processDocument, $dbForProject, $database, $authorization) {
+ $processDocument = function (Document $collection, Document $document) use (&$processDocument, $dbForProject, $database) {
$document->setAttribute('$databaseId', $database->getId());
$document->setAttribute('$collectionId', $collection->getId());
@@ -3042,7 +3005,7 @@ Http::post('/v1/databases/:databaseId/collections/:collectionId/documents')
}
$relatedCollectionId = $relationship->getAttribute('relatedCollection');
- $relatedCollection = $authorization->skip(
+ $relatedCollection = Authorization::skip(
fn () => $dbForProject->getDocument('database_' . $database->getInternalId(), $relatedCollectionId)
);
@@ -3076,9 +3039,12 @@ Http::post('/v1/databases/:databaseId/collections/:collectionId/documents')
->setContext('database', $database)
->setPayload($response->getPayload(), sensitive: $relationships);
+
+ $queueForUsage
+ ->addMetric(str_replace(['{databaseInternalId}', '{collectionInternalId}'], [$database->getInternalId(), $collection->getInternalId()], METRIC_DATABASE_ID_COLLECTION_ID_STORAGE), 1); // per collection
});
-Http::get('/v1/databases/:databaseId/collections/:collectionId/documents')
+App::get('/v1/databases/:databaseId/collections/:collectionId/documents')
->alias('/v1/database/collections/:collectionId/documents', ['databaseId' => 'default'])
->desc('List documents')
->groups(['api', 'database'])
@@ -3097,17 +3063,16 @@ Http::get('/v1/databases/:databaseId/collections/:collectionId/documents')
->inject('response')
->inject('dbForProject')
->inject('mode')
- ->inject('authorization')
- ->action(function (string $databaseId, string $collectionId, array $queries, Response $response, Database $dbForProject, string $mode, Authorization $authorization) {
- $database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId));
- $isAPIKey = Auth::isAppUser($authorization->getRoles());
- $isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles());
+ ->action(function (string $databaseId, string $collectionId, array $queries, Response $response, Database $dbForProject, string $mode) {
+ $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
+ $isAPIKey = Auth::isAppUser(Authorization::getRoles());
+ $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
if ($database->isEmpty() || (!$database->getAttribute('enabled', false) && !$isAPIKey && !$isPrivilegedUser)) {
throw new Exception(Exception::DATABASE_NOT_FOUND);
}
- $collection = $authorization->skip(fn () => $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId));
+ $collection = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId));
if ($collection->isEmpty() || (!$collection->getAttribute('enabled', false) && !$isAPIKey && !$isPrivilegedUser)) {
throw new Exception(Exception::COLLECTION_NOT_FOUND);
@@ -3131,7 +3096,7 @@ Http::get('/v1/databases/:databaseId/collections/:collectionId/documents')
if ($cursor) {
$documentId = $cursor->getValue();
- $cursorDocument = $authorization->skip(fn () => $dbForProject->getDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $documentId));
+ $cursorDocument = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $documentId));
if ($cursorDocument->isEmpty()) {
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Document '{$documentId}' for the 'cursor' value not found.");
@@ -3150,7 +3115,7 @@ Http::get('/v1/databases/:databaseId/collections/:collectionId/documents')
}
// Add $collectionId and $databaseId for all documents
- $processDocument = (function (Document $collection, Document $document) use (&$processDocument, $dbForProject, $database, $authorization): bool {
+ $processDocument = (function (Document $collection, Document $document) use (&$processDocument, $dbForProject, $database): bool {
if ($document->isEmpty()) {
return false;
}
@@ -3177,7 +3142,7 @@ Http::get('/v1/databases/:databaseId/collections/:collectionId/documents')
}
$relatedCollectionId = $relationship->getAttribute('relatedCollection');
- $relatedCollection = $authorization->skip(fn () => $dbForProject->getDocument('database_' . $database->getInternalId(), $relatedCollectionId));
+ $relatedCollection = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getInternalId(), $relatedCollectionId));
foreach ($relations as $index => $doc) {
if ($doc instanceof Document) {
@@ -3234,7 +3199,7 @@ Http::get('/v1/databases/:databaseId/collections/:collectionId/documents')
]), Response::MODEL_DOCUMENT_LIST);
});
-Http::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documentId')
+App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documentId')
->alias('/v1/database/collections/:collectionId/documents/:documentId', ['databaseId' => 'default'])
->desc('Get document')
->groups(['api', 'database'])
@@ -3255,18 +3220,17 @@ Http::get('/v1/databases/:databaseId/collections/:collectionId/documents/:docume
->inject('response')
->inject('dbForProject')
->inject('mode')
- ->inject('authorization')
- ->action(function (string $databaseId, string $collectionId, string $documentId, array $queries, Response $response, Database $dbForProject, string $mode, Authorization $authorization) {
- $database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId));
+ ->action(function (string $databaseId, string $collectionId, string $documentId, array $queries, Response $response, Database $dbForProject, string $mode) {
+ $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
- $isAPIKey = Auth::isAppUser($authorization->getRoles());
- $isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles());
+ $isAPIKey = Auth::isAppUser(Authorization::getRoles());
+ $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
if ($database->isEmpty() || (!$database->getAttribute('enabled', false) && !$isAPIKey && !$isPrivilegedUser)) {
throw new Exception(Exception::DATABASE_NOT_FOUND);
}
- $collection = $authorization->skip(fn () => $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId));
+ $collection = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId));
if ($collection->isEmpty() || (!$collection->getAttribute('enabled', false) && !$isAPIKey && !$isPrivilegedUser)) {
throw new Exception(Exception::COLLECTION_NOT_FOUND);
@@ -3286,7 +3250,7 @@ Http::get('/v1/databases/:databaseId/collections/:collectionId/documents/:docume
}
// Add $collectionId and $databaseId for all documents
- $processDocument = function (Document $collection, Document $document) use (&$processDocument, $dbForProject, $database, $authorization) {
+ $processDocument = function (Document $collection, Document $document) use (&$processDocument, $dbForProject, $database) {
if ($document->isEmpty()) {
return;
}
@@ -3310,7 +3274,7 @@ Http::get('/v1/databases/:databaseId/collections/:collectionId/documents/:docume
}
$relatedCollectionId = $relationship->getAttribute('relatedCollection');
- $relatedCollection = $authorization->skip(
+ $relatedCollection = Authorization::skip(
fn () => $dbForProject->getDocument('database_' . $database->getInternalId(), $relatedCollectionId)
);
@@ -3327,7 +3291,7 @@ Http::get('/v1/databases/:databaseId/collections/:collectionId/documents/:docume
$response->dynamic($document, Response::MODEL_DOCUMENT);
});
-Http::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documentId/logs')
+App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documentId/logs')
->alias('/v1/database/collections/:collectionId/documents/:documentId/logs', ['databaseId' => 'default'])
->desc('List document logs')
->groups(['api', 'database'])
@@ -3347,10 +3311,9 @@ Http::get('/v1/databases/:databaseId/collections/:collectionId/documents/:docume
->inject('dbForProject')
->inject('locale')
->inject('geodb')
- ->inject('authorization')
- ->action(function (string $databaseId, string $collectionId, string $documentId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb, Authorization $authorization) {
+ ->action(function (string $databaseId, string $collectionId, string $documentId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) {
- $database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId));
+ $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
if ($database->isEmpty()) {
throw new Exception(Exception::DATABASE_NOT_FOUND);
@@ -3432,7 +3395,7 @@ Http::get('/v1/databases/:databaseId/collections/:collectionId/documents/:docume
]), Response::MODEL_LOG_LIST);
});
-Http::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:documentId')
+App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:documentId')
->alias('/v1/database/collections/:collectionId/documents/:documentId', ['databaseId' => 'default'])
->desc('Update document')
->groups(['api', 'database'])
@@ -3462,8 +3425,7 @@ Http::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docu
->inject('dbForProject')
->inject('queueForEvents')
->inject('mode')
- ->inject('authorization')
- ->action(function (string $databaseId, string $collectionId, string $documentId, string|array $data, ?array $permissions, ?\DateTime $requestTimestamp, Response $response, Database $dbForProject, Event $queueForEvents, string $mode, Authorization $authorization) {
+ ->action(function (string $databaseId, string $collectionId, string $documentId, string|array $data, ?array $permissions, ?\DateTime $requestTimestamp, Response $response, Database $dbForProject, Event $queueForEvents, string $mode) {
$data = (\is_string($data)) ? \json_decode($data, true) : $data; // Cast to JSON array
@@ -3471,16 +3433,16 @@ Http::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docu
throw new Exception(Exception::DOCUMENT_MISSING_PAYLOAD);
}
- $database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId));
+ $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
- $isAPIKey = Auth::isAppUser($authorization->getRoles());
- $isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles());
+ $isAPIKey = Auth::isAppUser(Authorization::getRoles());
+ $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
if ($database->isEmpty() || (!$database->getAttribute('enabled', false) && !$isAPIKey && !$isPrivilegedUser)) {
throw new Exception(Exception::DATABASE_NOT_FOUND);
}
- $collection = $authorization->skip(fn () => $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId));
+ $collection = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId));
if ($collection->isEmpty() || (!$collection->getAttribute('enabled', false) && !$isAPIKey && !$isPrivilegedUser)) {
throw new Exception(Exception::COLLECTION_NOT_FOUND);
@@ -3488,7 +3450,7 @@ Http::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docu
// Read permission should not be required for update
/** @var Document $document */
- $document = $authorization->skip(fn () => $dbForProject->getDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $documentId));
+ $document = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $documentId));
if ($document->isEmpty()) {
throw new Exception(Exception::DOCUMENT_NOT_FOUND);
@@ -3502,7 +3464,7 @@ Http::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docu
]);
// Users can only manage their own roles, API keys and Admin users can manage any
- $roles = $authorization->getRoles();
+ $roles = Authorization::getRoles();
if (!$isAPIKey && !$isPrivilegedUser && !\is_null($permissions)) {
foreach (Database::PERMISSIONS as $type) {
foreach ($permissions as $permission) {
@@ -3515,7 +3477,7 @@ Http::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docu
$permission->getIdentifier(),
$permission->getDimension()
))->toString();
- if (!$authorization->isRole($role)) {
+ if (!Authorization::isRole($role)) {
throw new Exception(Exception::USER_UNAUTHORIZED, 'Permissions must be one of: (' . \implode(', ', $roles) . ')');
}
}
@@ -3530,7 +3492,7 @@ Http::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docu
$data['$permissions'] = $permissions;
$newDocument = new Document($data);
- $setCollection = (function (Document $collection, Document $document) use (&$setCollection, $dbForProject, $database, $authorization) {
+ $setCollection = (function (Document $collection, Document $document) use (&$setCollection, $dbForProject, $database) {
$relationships = \array_filter(
$collection->getAttribute('attributes', []),
fn ($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP
@@ -3552,7 +3514,7 @@ Http::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docu
}
$relatedCollectionId = $relationship->getAttribute('relatedCollection');
- $relatedCollection = $authorization->skip(
+ $relatedCollection = Authorization::skip(
fn () => $dbForProject->getDocument('database_' . $database->getInternalId(), $relatedCollectionId)
);
@@ -3567,7 +3529,7 @@ Http::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docu
$relation = new Document($relation);
}
if ($relation instanceof Document) {
- $oldDocument = $authorization->skip(fn () => $dbForProject->getDocument(
+ $oldDocument = Authorization::skip(fn () => $dbForProject->getDocument(
'database_' . $database->getInternalId() . '_collection_' . $relatedCollection->getInternalId(),
$relation->getId()
));
@@ -3616,7 +3578,7 @@ Http::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docu
}
// Add $collectionId and $databaseId for all documents
- $processDocument = function (Document $collection, Document $document) use (&$processDocument, $dbForProject, $database, $authorization) {
+ $processDocument = function (Document $collection, Document $document) use (&$processDocument, $dbForProject, $database) {
$document->setAttribute('$databaseId', $database->getId());
$document->setAttribute('$collectionId', $collection->getId());
@@ -3636,7 +3598,7 @@ Http::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docu
}
$relatedCollectionId = $relationship->getAttribute('relatedCollection');
- $relatedCollection = $authorization->skip(
+ $relatedCollection = Authorization::skip(
fn () => $dbForProject->getDocument('database_' . $database->getInternalId(), $relatedCollectionId)
);
@@ -3669,7 +3631,7 @@ Http::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docu
->setPayload($response->getPayload(), sensitive: $relationships);
});
-Http::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:documentId')
+App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:documentId')
->alias('/v1/database/collections/:collectionId/documents/:documentId', ['databaseId' => 'default'])
->desc('Delete document')
->groups(['api', 'database'])
@@ -3696,26 +3658,26 @@ Http::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:doc
->inject('dbForProject')
->inject('queueForDeletes')
->inject('queueForEvents')
+ ->inject('queueForUsage')
->inject('mode')
- ->inject('authorization')
- ->action(function (string $databaseId, string $collectionId, string $documentId, ?\DateTime $requestTimestamp, Response $response, Database $dbForProject, Delete $queueForDeletes, Event $queueForEvents, string $mode, Authorization $authorization) {
- $database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId));
+ ->action(function (string $databaseId, string $collectionId, string $documentId, ?\DateTime $requestTimestamp, Response $response, Database $dbForProject, Delete $queueForDeletes, Event $queueForEvents, Usage $queueForUsage, string $mode) {
+ $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
- $isAPIKey = Auth::isAppUser($authorization->getRoles());
- $isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles());
+ $isAPIKey = Auth::isAppUser(Authorization::getRoles());
+ $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
if ($database->isEmpty() || (!$database->getAttribute('enabled', false) && !$isAPIKey && !$isPrivilegedUser)) {
throw new Exception(Exception::DATABASE_NOT_FOUND);
}
- $collection = $authorization->skip(fn () => $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId));
+ $collection = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId));
if ($collection->isEmpty() || (!$collection->getAttribute('enabled', false) && !$isAPIKey && !$isPrivilegedUser)) {
throw new Exception(Exception::COLLECTION_NOT_FOUND);
}
// Read permission should not be required for delete
- $document = $authorization->skip(fn () => $dbForProject->getDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $documentId));
+ $document = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $documentId));
if ($document->isEmpty()) {
throw new Exception(Exception::DOCUMENT_NOT_FOUND);
@@ -3729,7 +3691,7 @@ Http::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:doc
});
// Add $collectionId and $databaseId for all documents
- $processDocument = function (Document $collection, Document $document) use (&$processDocument, $dbForProject, $database, $authorization) {
+ $processDocument = function (Document $collection, Document $document) use (&$processDocument, $dbForProject, $database) {
$document->setAttribute('$databaseId', $database->getId());
$document->setAttribute('$collectionId', $collection->getId());
@@ -3749,7 +3711,7 @@ Http::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:doc
}
$relatedCollectionId = $relationship->getAttribute('relatedCollection');
- $relatedCollection = $authorization->skip(
+ $relatedCollection = Authorization::skip(
fn () => $dbForProject->getDocument('database_' . $database->getInternalId(), $relatedCollectionId)
);
@@ -3783,10 +3745,13 @@ Http::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:doc
->setContext('database', $database)
->setPayload($response->output($document, Response::MODEL_DOCUMENT), sensitive: $relationships);
+ $queueForUsage
+ ->addMetric(str_replace(['{databaseInternalId}', '{collectionInternalId}'], [$database->getInternalId(), $collection->getInternalId()], METRIC_DATABASE_ID_COLLECTION_ID_STORAGE), 1); // per collection
+
$response->noContent();
});
-Http::get('/v1/databases/usage')
+App::get('/v1/databases/usage')
->desc('Get databases usage stats')
->groups(['api', 'database', 'usage'])
->label('scope', 'collections.read')
@@ -3799,8 +3764,7 @@ Http::get('/v1/databases/usage')
->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), '`Date range.', true)
->inject('response')
->inject('dbForProject')
- ->inject('authorization')
- ->action(function (string $range, Response $response, Database $dbForProject, Authorization $authorization) {
+ ->action(function (string $range, Response $response, Database $dbForProject) {
$periods = Config::getParam('usage', []);
$stats = $usage = [];
@@ -3809,9 +3773,10 @@ Http::get('/v1/databases/usage')
METRIC_DATABASES,
METRIC_COLLECTIONS,
METRIC_DOCUMENTS,
+ METRIC_DATABASES_STORAGE
];
- $authorization->skip(function () use ($dbForProject, $days, $metrics, &$stats) {
+ Authorization::skip(function () use ($dbForProject, $days, $metrics, &$stats) {
foreach ($metrics as $metric) {
$result = $dbForProject->findOne('stats', [
Query::equal('metric', [$metric]),
@@ -3859,13 +3824,15 @@ Http::get('/v1/databases/usage')
'databasesTotal' => $usage[$metrics[0]]['total'],
'collectionsTotal' => $usage[$metrics[1]]['total'],
'documentsTotal' => $usage[$metrics[2]]['total'],
+ 'storageTotal' => $usage[$metrics[3]]['total'],
'databases' => $usage[$metrics[0]]['data'],
'collections' => $usage[$metrics[1]]['data'],
'documents' => $usage[$metrics[2]]['data'],
+ 'storage' => $usage[$metrics[3]]['data'],
]), Response::MODEL_USAGE_DATABASES);
});
-Http::get('/v1/databases/:databaseId/usage')
+App::get('/v1/databases/:databaseId/usage')
->desc('Get database usage stats')
->groups(['api', 'database', 'usage'])
->label('scope', 'collections.read')
@@ -3879,8 +3846,7 @@ Http::get('/v1/databases/:databaseId/usage')
->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), '`Date range.', true)
->inject('response')
->inject('dbForProject')
- ->inject('authorization')
- ->action(function (string $databaseId, string $range, Response $response, Database $dbForProject, Authorization $authorization) {
+ ->action(function (string $databaseId, string $range, Response $response, Database $dbForProject) {
$database = $dbForProject->getDocument('databases', $databaseId);
@@ -3894,9 +3860,10 @@ Http::get('/v1/databases/:databaseId/usage')
$metrics = [
str_replace('{databaseInternalId}', $database->getInternalId(), METRIC_DATABASE_ID_COLLECTIONS),
str_replace('{databaseInternalId}', $database->getInternalId(), METRIC_DATABASE_ID_DOCUMENTS),
+ str_replace('{databaseInternalId}', $database->getInternalId(), METRIC_DATABASE_ID_STORAGE)
];
- $authorization->skip(function () use ($dbForProject, $days, $metrics, &$stats) {
+ Authorization::skip(function () use ($dbForProject, $days, $metrics, &$stats) {
foreach ($metrics as $metric) {
$result = $dbForProject->findOne('stats', [
Query::equal('metric', [$metric]),
@@ -3944,12 +3911,14 @@ Http::get('/v1/databases/:databaseId/usage')
'range' => $range,
'collectionsTotal' => $usage[$metrics[0]]['total'],
'documentsTotal' => $usage[$metrics[1]]['total'],
+ 'storageTotal' => $usage[$metrics[2]]['total'],
'collections' => $usage[$metrics[0]]['data'],
'documents' => $usage[$metrics[1]]['data'],
+ 'storage' => $usage[$metrics[2]]['data'],
]), Response::MODEL_USAGE_DATABASE);
});
-Http::get('/v1/databases/:databaseId/collections/:collectionId/usage')
+App::get('/v1/databases/:databaseId/collections/:collectionId/usage')
->alias('/v1/database/:collectionId/usage', ['databaseId' => 'default'])
->desc('Get collection usage stats')
->groups(['api', 'database', 'usage'])
@@ -3965,8 +3934,7 @@ Http::get('/v1/databases/:databaseId/collections/:collectionId/usage')
->param('collectionId', '', new UID(), 'Collection ID.')
->inject('response')
->inject('dbForProject')
- ->inject('authorization')
- ->action(function (string $databaseId, string $range, string $collectionId, Response $response, Database $dbForProject, Authorization $authorization) {
+ ->action(function (string $databaseId, string $range, string $collectionId, Response $response, Database $dbForProject) {
$database = $dbForProject->getDocument('databases', $databaseId);
$collectionDocument = $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId);
@@ -3983,7 +3951,7 @@ Http::get('/v1/databases/:databaseId/collections/:collectionId/usage')
str_replace(['{databaseInternalId}', '{collectionInternalId}'], [$database->getInternalId(), $collectionDocument->getInternalId()], METRIC_DATABASE_ID_COLLECTION_ID_DOCUMENTS),
];
- $authorization->skip(function () use ($dbForProject, $days, $metrics, &$stats) {
+ Authorization::skip(function () use ($dbForProject, $days, $metrics, &$stats) {
foreach ($metrics as $metric) {
$result = $dbForProject->findOne('stats', [
Query::equal('metric', [$metric]),
diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php
index a4cf80c913..9c3e6782b4 100644
--- a/app/controllers/api/functions.php
+++ b/app/controllers/api/functions.php
@@ -2,7 +2,6 @@
use Ahc\Jwt\JWT;
use Appwrite\Auth\Auth;
-use Appwrite\Auth\Authentication;
use Appwrite\Event\Build;
use Appwrite\Event\Delete;
use Appwrite\Event\Event;
@@ -21,11 +20,11 @@ use Appwrite\Utopia\Database\Validator\CustomId;
use Appwrite\Utopia\Database\Validator\Queries\Deployments;
use Appwrite\Utopia\Database\Validator\Queries\Executions;
use Appwrite\Utopia\Database\Validator\Queries\Functions;
-use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model\Rule;
use Executor\Executor;
use MaxMind\Db\Reader;
+use Utopia\App;
use Utopia\CLI\Console;
use Utopia\Config\Config;
use Utopia\Database\Database;
@@ -38,25 +37,24 @@ use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
-use Utopia\Database\Validator\Authorization\Input;
use Utopia\Database\Validator\Datetime as DatetimeValidator;
use Utopia\Database\Validator\Roles;
use Utopia\Database\Validator\UID;
-use Utopia\Http\Http;
-use Utopia\Http\Validator\AnyOf;
-use Utopia\Http\Validator\ArrayList;
-use Utopia\Http\Validator\Assoc;
-use Utopia\Http\Validator\Boolean;
-use Utopia\Http\Validator\Nullable;
-use Utopia\Http\Validator\Range;
-use Utopia\Http\Validator\Text;
-use Utopia\Http\Validator\WhiteList;
use Utopia\Storage\Device;
use Utopia\Storage\Validator\File;
use Utopia\Storage\Validator\FileExt;
use Utopia\Storage\Validator\FileSize;
use Utopia\Storage\Validator\Upload;
+use Utopia\Swoole\Request;
use Utopia\System\System;
+use Utopia\Validator\AnyOf;
+use Utopia\Validator\ArrayList;
+use Utopia\Validator\Assoc;
+use Utopia\Validator\Boolean;
+use Utopia\Validator\Nullable;
+use Utopia\Validator\Range;
+use Utopia\Validator\Text;
+use Utopia\Validator\WhiteList;
use Utopia\VCS\Adapter\Git\GitHub;
use Utopia\VCS\Exception\RepositoryNotFound;
@@ -135,7 +133,7 @@ $redeployVcs = function (Request $request, Document $function, Document $project
->setTemplate($template);
};
-Http::post('/v1/functions')
+App::post('/v1/functions')
->groups(['api', 'functions'])
->desc('Create function')
->label('scope', 'functions.write')
@@ -173,8 +171,8 @@ Http::post('/v1/functions')
->param('specification', APP_FUNCTION_SPECIFICATION_DEFAULT, fn (array $plan) => new RuntimeSpecification(
$plan,
Config::getParam('runtime-specifications', []),
- System::getEnv('_APP_FUNCTIONS_CPUS', APP_FUNCTION_CPUS_DEFAULT),
- System::getEnv('_APP_FUNCTIONS_MEMORY', APP_FUNCTION_MEMORY_DEFAULT)
+ App::getEnv('_APP_FUNCTIONS_CPUS', APP_FUNCTION_CPUS_DEFAULT),
+ App::getEnv('_APP_FUNCTIONS_MEMORY', APP_FUNCTION_MEMORY_DEFAULT)
), 'Runtime specification for the function and builds.', true, ['plan'])
->inject('request')
->inject('response')
@@ -185,8 +183,7 @@ Http::post('/v1/functions')
->inject('queueForBuilds')
->inject('dbForConsole')
->inject('gitHub')
- ->inject('authorization')
- ->action(function (string $functionId, string $name, string $runtime, array $execute, array $events, string $schedule, int $timeout, bool $enabled, bool $logging, string $entrypoint, string $commands, array $scopes, string $installationId, string $providerRepositoryId, string $providerBranch, bool $providerSilentMode, string $providerRootDirectory, string $templateRepository, string $templateOwner, string $templateRootDirectory, string $templateVersion, string $specification, Request $request, Response $response, Database $dbForProject, Document $project, Document $user, Event $queueForEvents, Build $queueForBuilds, Database $dbForConsole, GitHub $github, Authorization $authorization) use ($redeployVcs) {
+ ->action(function (string $functionId, string $name, string $runtime, array $execute, array $events, string $schedule, int $timeout, bool $enabled, bool $logging, string $entrypoint, string $commands, array $scopes, string $installationId, string $providerRepositoryId, string $providerBranch, bool $providerSilentMode, string $providerRootDirectory, string $templateRepository, string $templateOwner, string $templateRootDirectory, string $templateVersion, string $specification, Request $request, Response $response, Database $dbForProject, Document $project, Document $user, Event $queueForEvents, Build $queueForBuilds, Database $dbForConsole, GitHub $github) use ($redeployVcs) {
$functionId = ($functionId == 'unique()') ? ID::unique() : $functionId;
$allowList = \array_filter(\explode(',', System::getEnv('_APP_FUNCTIONS_RUNTIMES', '')));
@@ -250,7 +247,7 @@ Http::post('/v1/functions')
'specification' => $specification
]));
- $schedule = $authorization->skip(
+ $schedule = Authorization::skip(
fn () => $dbForConsole->createDocument('schedules', new Document([
'region' => System::getEnv('_APP_REGION', 'default'), // Todo replace with projects region
'resourceType' => 'function',
@@ -332,7 +329,7 @@ Http::post('/v1/functions')
$routeSubdomain = ID::unique();
$domain = "{$routeSubdomain}.{$functionsDomain}";
- $rule = $authorization->skip(
+ $rule = Authorization::skip(
fn () => $dbForConsole->createDocument('rules', new Document([
'$id' => $ruleId,
'projectId' => $project->getId(),
@@ -399,7 +396,7 @@ Http::post('/v1/functions')
->dynamic($function, Response::MODEL_FUNCTION);
});
-Http::get('/v1/functions')
+App::get('/v1/functions')
->groups(['api', 'functions'])
->desc('List functions')
->label('scope', 'functions.read')
@@ -453,7 +450,7 @@ Http::get('/v1/functions')
]), Response::MODEL_FUNCTION_LIST);
});
-Http::get('/v1/functions/runtimes')
+App::get('/v1/functions/runtimes')
->groups(['api', 'functions'])
->desc('List runtimes')
->label('scope', 'functions.read')
@@ -486,7 +483,7 @@ Http::get('/v1/functions/runtimes')
]), Response::MODEL_RUNTIME_LIST);
});
-Http::get('/v1/functions/specifications')
+App::get('/v1/functions/specifications')
->groups(['api', 'functions'])
->desc('List available function runtime specifications')
->label('scope', 'functions.read')
@@ -522,7 +519,7 @@ Http::get('/v1/functions/specifications')
]), Response::MODEL_SPECIFICATION_LIST);
});
-Http::get('/v1/functions/:functionId')
+App::get('/v1/functions/:functionId')
->groups(['api', 'functions'])
->desc('Get function')
->label('scope', 'functions.read')
@@ -546,7 +543,7 @@ Http::get('/v1/functions/:functionId')
$response->dynamic($function, Response::MODEL_FUNCTION);
});
-Http::get('/v1/functions/:functionId/usage')
+App::get('/v1/functions/:functionId/usage')
->desc('Get function usage')
->groups(['api', 'functions', 'usage'])
->label('scope', 'functions.read')
@@ -560,8 +557,7 @@ Http::get('/v1/functions/:functionId/usage')
->param('range', '30d', new WhiteList(['24h', '30d', '90d']), 'Date range.', true)
->inject('response')
->inject('dbForProject')
- ->inject('authorization')
- ->action(function (string $functionId, string $range, Response $response, Database $dbForProject, Authorization $authorization) {
+ ->action(function (string $functionId, string $range, Response $response, Database $dbForProject) {
$function = $dbForProject->getDocument('functions', $functionId);
@@ -584,7 +580,7 @@ Http::get('/v1/functions/:functionId/usage')
str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS_MB_SECONDS)
];
- $authorization->skip(function () use ($dbForProject, $days, $metrics, &$stats) {
+ Authorization::skip(function () use ($dbForProject, $days, $metrics, &$stats) {
foreach ($metrics as $metric) {
$result = $dbForProject->findOne('stats', [
Query::equal('metric', [$metric]),
@@ -651,7 +647,7 @@ Http::get('/v1/functions/:functionId/usage')
]), Response::MODEL_USAGE_FUNCTION);
});
-Http::get('/v1/functions/usage')
+App::get('/v1/functions/usage')
->desc('Get functions usage')
->groups(['api', 'functions'])
->label('scope', 'functions.read')
@@ -664,8 +660,7 @@ Http::get('/v1/functions/usage')
->param('range', '30d', new WhiteList(['24h', '30d', '90d']), 'Date range.', true)
->inject('response')
->inject('dbForProject')
- ->inject('authorization')
- ->action(function (string $range, Response $response, Database $dbForProject, Authorization $authorization) {
+ ->action(function (string $range, Response $response, Database $dbForProject) {
$periods = Config::getParam('usage', []);
$stats = $usage = [];
@@ -683,7 +678,7 @@ Http::get('/v1/functions/usage')
METRIC_EXECUTIONS_MB_SECONDS,
];
- $authorization->skip(function () use ($dbForProject, $days, $metrics, &$stats) {
+ Authorization::skip(function () use ($dbForProject, $days, $metrics, &$stats) {
foreach ($metrics as $metric) {
$result = $dbForProject->findOne('stats', [
Query::equal('metric', [$metric]),
@@ -751,7 +746,7 @@ Http::get('/v1/functions/usage')
]), Response::MODEL_USAGE_FUNCTIONS);
});
-Http::put('/v1/functions/:functionId')
+App::put('/v1/functions/:functionId')
->groups(['api', 'functions'])
->desc('Update function')
->label('scope', 'functions.write')
@@ -785,8 +780,8 @@ Http::put('/v1/functions/:functionId')
->param('specification', APP_FUNCTION_SPECIFICATION_DEFAULT, fn (array $plan) => new RuntimeSpecification(
$plan,
Config::getParam('runtime-specifications', []),
- System::getEnv('_APP_FUNCTIONS_CPUS', APP_FUNCTION_CPUS_DEFAULT),
- System::getEnv('_APP_FUNCTIONS_MEMORY', APP_FUNCTION_MEMORY_DEFAULT)
+ App::getEnv('_APP_FUNCTIONS_CPUS', APP_FUNCTION_CPUS_DEFAULT),
+ App::getEnv('_APP_FUNCTIONS_MEMORY', APP_FUNCTION_MEMORY_DEFAULT)
), 'Runtime specification for the function and builds.', true, ['plan'])
->inject('request')
->inject('response')
@@ -796,8 +791,7 @@ Http::put('/v1/functions/:functionId')
->inject('queueForBuilds')
->inject('dbForConsole')
->inject('gitHub')
- ->inject('authorization')
- ->action(function (string $functionId, string $name, string $runtime, array $execute, array $events, string $schedule, int $timeout, bool $enabled, bool $logging, string $entrypoint, string $commands, array $scopes, string $installationId, ?string $providerRepositoryId, string $providerBranch, bool $providerSilentMode, string $providerRootDirectory, string $specification, Request $request, Response $response, Database $dbForProject, Document $project, Event $queueForEvents, Build $queueForBuilds, Database $dbForConsole, GitHub $github, Authorization $authorization) use ($redeployVcs) {
+ ->action(function (string $functionId, string $name, string $runtime, array $execute, array $events, string $schedule, int $timeout, bool $enabled, bool $logging, string $entrypoint, string $commands, array $scopes, string $installationId, ?string $providerRepositoryId, string $providerBranch, bool $providerSilentMode, string $providerRootDirectory, string $specification, Request $request, Response $response, Database $dbForProject, Document $project, Event $queueForEvents, Build $queueForBuilds, Database $dbForConsole, GitHub $github) use ($redeployVcs) {
// TODO: If only branch changes, re-deploy
$function = $dbForProject->getDocument('functions', $functionId);
@@ -900,7 +894,7 @@ Http::put('/v1/functions/:functionId')
// Enforce Cold Start if spec limits change.
if ($function->getAttribute('specification') !== $specification && !empty($function->getAttribute('deployment'))) {
- $executor = new Executor(System::getEnv('_APP_EXECUTOR_HOST'));
+ $executor = new Executor(App::getEnv('_APP_EXECUTOR_HOST'));
try {
$executor->deleteRuntime($project->getId(), $function->getAttribute('deployment'));
} catch (\Throwable $th) {
@@ -947,14 +941,14 @@ Http::put('/v1/functions/:functionId')
->setAttribute('resourceUpdatedAt', DateTime::now())
->setAttribute('schedule', $function->getAttribute('schedule'))
->setAttribute('active', !empty($function->getAttribute('schedule')) && !empty($function->getAttribute('deployment')));
- $authorization->skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule));
+ Authorization::skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule));
$queueForEvents->setParam('functionId', $function->getId());
$response->dynamic($function, Response::MODEL_FUNCTION);
});
-Http::get('/v1/functions/:functionId/deployments/:deploymentId/download')
+App::get('/v1/functions/:functionId/deployments/:deploymentId/download')
->groups(['api', 'functions'])
->desc('Download deployment')
->label('scope', 'functions.read')
@@ -1039,7 +1033,7 @@ Http::get('/v1/functions/:functionId/deployments/:deploymentId/download')
}
});
-Http::patch('/v1/functions/:functionId/deployments/:deploymentId')
+App::patch('/v1/functions/:functionId/deployments/:deploymentId')
->groups(['api', 'functions'])
->desc('Update deployment')
->label('scope', 'functions.write')
@@ -1059,8 +1053,7 @@ Http::patch('/v1/functions/:functionId/deployments/:deploymentId')
->inject('dbForProject')
->inject('queueForEvents')
->inject('dbForConsole')
- ->inject('authorization')
- ->action(function (string $functionId, string $deploymentId, Response $response, Database $dbForProject, Event $queueForEvents, Database $dbForConsole, Authorization $authorization) {
+ ->action(function (string $functionId, string $deploymentId, Response $response, Database $dbForProject, Event $queueForEvents, Database $dbForConsole) {
$function = $dbForProject->getDocument('functions', $functionId);
$deployment = $dbForProject->getDocument('deployments', $deploymentId);
@@ -1093,7 +1086,7 @@ Http::patch('/v1/functions/:functionId/deployments/:deploymentId')
->setAttribute('resourceUpdatedAt', DateTime::now())
->setAttribute('schedule', $function->getAttribute('schedule'))
->setAttribute('active', !empty($function->getAttribute('schedule')) && !empty($function->getAttribute('deployment')));
- $authorization->skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule));
+ Authorization::skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule));
$queueForEvents
->setParam('functionId', $function->getId())
@@ -1102,7 +1095,7 @@ Http::patch('/v1/functions/:functionId/deployments/:deploymentId')
$response->dynamic($function, Response::MODEL_FUNCTION);
});
-Http::delete('/v1/functions/:functionId')
+App::delete('/v1/functions/:functionId')
->groups(['api', 'functions'])
->desc('Delete function')
->label('scope', 'functions.write')
@@ -1121,8 +1114,7 @@ Http::delete('/v1/functions/:functionId')
->inject('queueForDeletes')
->inject('queueForEvents')
->inject('dbForConsole')
- ->inject('authorization')
- ->action(function (string $functionId, Response $response, Database $dbForProject, Delete $queueForDeletes, Event $queueForEvents, Database $dbForConsole, Authorization $authorization) {
+ ->action(function (string $functionId, Response $response, Database $dbForProject, Delete $queueForDeletes, Event $queueForEvents, Database $dbForConsole) {
$function = $dbForProject->getDocument('functions', $functionId);
@@ -1139,7 +1131,7 @@ Http::delete('/v1/functions/:functionId')
$schedule
->setAttribute('resourceUpdatedAt', DateTime::now())
->setAttribute('active', false);
- $authorization->skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule));
+ Authorization::skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule));
$queueForDeletes
->setType(DELETE_TYPE_DOCUMENT)
@@ -1150,7 +1142,7 @@ Http::delete('/v1/functions/:functionId')
$response->noContent();
});
-Http::post('/v1/functions/:functionId/deployments')
+App::post('/v1/functions/:functionId/deployments')
->groups(['api', 'functions'])
->desc('Create deployment')
->label('scope', 'functions.write')
@@ -1369,7 +1361,7 @@ Http::post('/v1/functions/:functionId/deployments')
->dynamic($deployment, Response::MODEL_DEPLOYMENT);
});
-Http::get('/v1/functions/:functionId/deployments')
+App::get('/v1/functions/:functionId/deployments')
->groups(['api', 'functions'])
->desc('List deployments')
->label('scope', 'functions.read')
@@ -1446,7 +1438,7 @@ Http::get('/v1/functions/:functionId/deployments')
]), Response::MODEL_DEPLOYMENT_LIST);
});
-Http::get('/v1/functions/:functionId/deployments/:deploymentId')
+App::get('/v1/functions/:functionId/deployments/:deploymentId')
->groups(['api', 'functions'])
->desc('Get deployment')
->label('scope', 'functions.read')
@@ -1489,7 +1481,7 @@ Http::get('/v1/functions/:functionId/deployments/:deploymentId')
$response->dynamic($deployment, Response::MODEL_DEPLOYMENT);
});
-Http::delete('/v1/functions/:functionId/deployments/:deploymentId')
+App::delete('/v1/functions/:functionId/deployments/:deploymentId')
->groups(['api', 'functions'])
->desc('Delete deployment')
->label('scope', 'functions.write')
@@ -1553,7 +1545,7 @@ Http::delete('/v1/functions/:functionId/deployments/:deploymentId')
$response->noContent();
});
-Http::post('/v1/functions/:functionId/deployments/:deploymentId/build')
+App::post('/v1/functions/:functionId/deployments/:deploymentId/build')
->alias('/v1/functions/:functionId/deployments/:deploymentId/builds/:buildId')
->groups(['api', 'functions'])
->desc('Rebuild deployment')
@@ -1576,8 +1568,7 @@ Http::post('/v1/functions/:functionId/deployments/:deploymentId/build')
->inject('queueForEvents')
->inject('queueForBuilds')
->inject('deviceForFunctions')
- ->inject('authorization')
- ->action(function (string $functionId, string $deploymentId, string $buildId, Request $request, Response $response, Database $dbForProject, Document $project, Event $queueForEvents, Build $queueForBuilds, Device $deviceForFunctions, Authorization $authorization) {
+ ->action(function (string $functionId, string $deploymentId, string $buildId, Request $request, Response $response, Database $dbForProject, Document $project, Event $queueForEvents, Build $queueForBuilds, Device $deviceForFunctions) {
$function = $dbForProject->getDocument('functions', $functionId);
if ($function->isEmpty()) {
@@ -1623,7 +1614,7 @@ Http::post('/v1/functions/:functionId/deployments/:deploymentId/build')
$response->noContent();
});
-Http::patch('/v1/functions/:functionId/deployments/:deploymentId/build')
+App::patch('/v1/functions/:functionId/deployments/:deploymentId/build')
->groups(['api', 'functions'])
->desc('Cancel deployment')
->label('scope', 'functions.write')
@@ -1641,8 +1632,7 @@ Http::patch('/v1/functions/:functionId/deployments/:deploymentId/build')
->inject('dbForProject')
->inject('project')
->inject('queueForEvents')
- ->inject('authorization')
- ->action(function (string $functionId, string $deploymentId, Response $response, Database $dbForProject, Document $project, Event $queueForEvents, Authorization $authorization) {
+ ->action(function (string $functionId, string $deploymentId, Response $response, Database $dbForProject, Document $project, Event $queueForEvents) {
$function = $dbForProject->getDocument('functions', $functionId);
if ($function->isEmpty()) {
@@ -1655,7 +1645,7 @@ Http::patch('/v1/functions/:functionId/deployments/:deploymentId/build')
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
}
- $build = $authorization->skip(fn () => $dbForProject->getDocument('builds', $deployment->getAttribute('buildId', '')));
+ $build = Authorization::skip(fn () => $dbForProject->getDocument('builds', $deployment->getAttribute('buildId', '')));
if ($build->isEmpty()) {
$buildId = ID::unique();
@@ -1697,7 +1687,7 @@ Http::patch('/v1/functions/:functionId/deployments/:deploymentId/build')
$dbForProject->purgeCachedDocument('deployments', $deployment->getId());
try {
- $executor = new Executor(System::getEnv('_APP_EXECUTOR_HOST'));
+ $executor = new Executor(App::getEnv('_APP_EXECUTOR_HOST'));
$executor->deleteRuntime($project->getId(), $deploymentId . "-build");
} catch (\Throwable $th) {
// Don't throw if the deployment doesn't exist
@@ -1713,7 +1703,7 @@ Http::patch('/v1/functions/:functionId/deployments/:deploymentId/build')
$response->dynamic($build, Response::MODEL_BUILD);
});
-Http::post('/v1/functions/:functionId/executions')
+App::post('/v1/functions/:functionId/executions')
->groups(['api', 'functions'])
->desc('Create execution')
->label('scope', 'execution.write')
@@ -1728,7 +1718,7 @@ Http::post('/v1/functions/:functionId/executions')
->label('sdk.request.type', Response::CONTENT_TYPE_JSON)
->param('functionId', '', new UID(), 'Function ID.')
->param('body', '', new Payload(10485760, 0), 'HTTP body of execution. Default value is empty string.', true)
- ->param('async', false, new Boolean(), 'Execute code in the background. Default value is false.', true)
+ ->param('async', false, new Boolean(true), 'Execute code in the background. Default value is false.', true)
->param('path', '/', new Text(2048), 'HTTP path of execution. Path can include query params. Default value is /', true)
->param('method', 'POST', new Whitelist(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'], true), 'HTTP method of execution. Default value is GET.', true)
->param('headers', [], new AnyOf([new Assoc(), new Text(65535)], AnyOf::TYPE_MIXED), 'HTTP headers of execution. Defaults to empty.', true)
@@ -1743,9 +1733,8 @@ Http::post('/v1/functions/:functionId/executions')
->inject('queueForUsage')
->inject('queueForFunctions')
->inject('geodb')
- ->inject('authorization')
- ->inject('authentication')
- ->action(function (string $functionId, string $body, bool $async, string $path, string $method, array $headers, ?string $scheduledAt, Response $response, Request $request, Document $project, Database $dbForProject, Database $dbForConsole, Document $user, Event $queueForEvents, Usage $queueForUsage, Func $queueForFunctions, Reader $geodb, Authorization $authorization, Authentication $authentication) {
+ ->action(function (string $functionId, string $body, mixed $async, string $path, string $method, mixed $headers, ?string $scheduledAt, Response $response, Request $request, Document $project, Database $dbForProject, Database $dbForConsole, Document $user, Event $queueForEvents, Usage $queueForUsage, Func $queueForFunctions, Reader $geodb) {
+ $async = \strval($async) === 'true' || \strval($async) === '1';
if (!$async && !is_null($scheduledAt)) {
throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Scheduled executions must run asynchronously. Set scheduledAt to a future date, or set async to true.');
@@ -1774,10 +1763,10 @@ Http::post('/v1/functions/:functionId/executions')
throw new Exception($validator->getDescription(), 400);
}
- $function = $authorization->skip(fn () => $dbForProject->getDocument('functions', $functionId));
+ $function = Authorization::skip(fn () => $dbForProject->getDocument('functions', $functionId));
- $isAPIKey = Auth::isAppUser($authorization->getRoles());
- $isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles());
+ $isAPIKey = Auth::isAppUser(Authorization::getRoles());
+ $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
if ($function->isEmpty() || (!$function->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) {
throw new Exception(Exception::FUNCTION_NOT_FOUND);
@@ -1793,7 +1782,7 @@ Http::post('/v1/functions/:functionId/executions')
throw new Exception(Exception::FUNCTION_RUNTIME_UNSUPPORTED, 'Runtime "' . $function->getAttribute('runtime', '') . '" is not supported');
}
- $deployment = $authorization->skip(fn () => $dbForProject->getDocument('deployments', $function->getAttribute('deployment', '')));
+ $deployment = Authorization::skip(fn () => $dbForProject->getDocument('deployments', $function->getAttribute('deployment', '')));
if ($deployment->getAttribute('resourceId') !== $function->getId()) {
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND, 'Deployment not found. Create a deployment before trying to execute a function');
@@ -1804,7 +1793,7 @@ Http::post('/v1/functions/:functionId/executions')
}
/** Check if build has completed */
- $build = $authorization->skip(fn () => $dbForProject->getDocument('builds', $deployment->getAttribute('buildId', '')));
+ $build = Authorization::skip(fn () => $dbForProject->getDocument('builds', $deployment->getAttribute('buildId', '')));
if ($build->isEmpty()) {
throw new Exception(Exception::BUILD_NOT_FOUND);
}
@@ -1813,8 +1802,10 @@ Http::post('/v1/functions/:functionId/executions')
throw new Exception(Exception::BUILD_NOT_READY);
}
- if (!$authorization->isValid(new Input('execute', $function->getAttribute('execute')))) { // Check if user has write access to execute function
- throw new Exception(Exception::USER_UNAUTHORIZED, $authorization->getDescription());
+ $validator = new Authorization('execute');
+
+ if (!$validator->isValid($function->getAttribute('execute'))) { // Check if user has write access to execute function
+ throw new Exception(Exception::USER_UNAUTHORIZED, $validator->getDescription());
}
$jwt = ''; // initialize
@@ -1824,7 +1815,7 @@ Http::post('/v1/functions/:functionId/executions')
foreach ($sessions as $session) {
/** @var Utopia\Database\Document $session */
- if ($session->getAttribute('secret') == Auth::hash($authentication->getSecret())) { // If current session delete the cookies too
+ if ($session->getAttribute('secret') == Auth::hash(Auth::$secret)) { // If current session delete the cookies too
$current = $session;
}
}
@@ -1908,9 +1899,8 @@ Http::post('/v1/functions/:functionId/executions')
->setContext('function', $function);
if ($async) {
-
if (is_null($scheduledAt)) {
- $execution = $authorization->skip(fn () => $dbForProject->createDocument('executions', $execution));
+ $execution = Authorization::skip(fn () => $dbForProject->createDocument('executions', $execution));
$queueForFunctions
->setType('http')
->setExecution($execution)
@@ -1931,7 +1921,7 @@ Http::post('/v1/functions/:functionId/executions')
'path' => $path,
'method' => $method,
'body' => $body,
- 'jwt' => $jwt,
+ 'userId' => $user->getId()
];
$schedule = $dbForConsole->createDocument('schedules', new Document([
@@ -1951,7 +1941,7 @@ Http::post('/v1/functions/:functionId/executions')
->setAttribute('scheduleInternalId', $schedule->getInternalId())
->setAttribute('scheduledAt', $scheduledAt);
- $execution = $authorization->skip(fn () => $dbForProject->createDocument('executions', $execution));
+ $execution = Authorization::skip(fn () => $dbForProject->createDocument('executions', $execution));
}
return $response
@@ -2037,7 +2027,8 @@ Http::post('/v1/functions/:functionId/executions')
runtimeEntrypoint: $command,
cpus: $spec['cpus'] ?? APP_FUNCTION_CPUS_DEFAULT,
memory: $spec['memory'] ?? APP_FUNCTION_MEMORY_DEFAULT,
- logging: $function->getAttribute('logging', true)
+ logging: $function->getAttribute('logging', true),
+ requestTimeout: 30
);
$headersFiltered = [];
@@ -2078,10 +2069,10 @@ Http::post('/v1/functions/:functionId/executions')
->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS_MB_SECONDS), (int)(($spec['memory'] ?? APP_FUNCTION_MEMORY_DEFAULT) * $execution->getAttribute('duration', 0) * ($spec['cpus'] ?? APP_FUNCTION_CPUS_DEFAULT)))
;
- $execution = $authorization->skip(fn () => $dbForProject->createDocument('executions', $execution));
+ $execution = Authorization::skip(fn () => $dbForProject->createDocument('executions', $execution));
}
- $roles = $authorization->getRoles();
+ $roles = Authorization::getRoles();
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
$isAppUser = Auth::isAppUser($roles);
@@ -2114,7 +2105,7 @@ Http::post('/v1/functions/:functionId/executions')
->dynamic($execution, Response::MODEL_EXECUTION);
});
-Http::get('/v1/functions/:functionId/executions')
+App::get('/v1/functions/:functionId/executions')
->groups(['api', 'functions'])
->desc('List executions')
->label('scope', 'execution.read')
@@ -2131,12 +2122,11 @@ Http::get('/v1/functions/:functionId/executions')
->inject('response')
->inject('dbForProject')
->inject('mode')
- ->inject('authorization')
- ->action(function (string $functionId, array $queries, string $search, Response $response, Database $dbForProject, string $mode, Authorization $authorization) {
- $function = $authorization->skip(fn () => $dbForProject->getDocument('functions', $functionId));
+ ->action(function (string $functionId, array $queries, string $search, Response $response, Database $dbForProject, string $mode) {
+ $function = Authorization::skip(fn () => $dbForProject->getDocument('functions', $functionId));
- $isAPIKey = Auth::isAppUser($authorization->getRoles());
- $isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles());
+ $isAPIKey = Auth::isAppUser(Authorization::getRoles());
+ $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
if ($function->isEmpty() || (!$function->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) {
throw new Exception(Exception::FUNCTION_NOT_FOUND);
@@ -2179,7 +2169,7 @@ Http::get('/v1/functions/:functionId/executions')
$results = $dbForProject->find('executions', $queries);
$total = $dbForProject->count('executions', $filterQueries, APP_LIMIT_COUNT);
- $roles = $authorization->getRoles();
+ $roles = Authorization::getRoles();
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
$isAppUser = Auth::isAppUser($roles);
if (!$isPrivilegedUser && !$isAppUser) {
@@ -2196,7 +2186,7 @@ Http::get('/v1/functions/:functionId/executions')
]), Response::MODEL_EXECUTION_LIST);
});
-Http::get('/v1/functions/:functionId/executions/:executionId')
+App::get('/v1/functions/:functionId/executions/:executionId')
->groups(['api', 'functions'])
->desc('Get execution')
->label('scope', 'execution.read')
@@ -2212,12 +2202,11 @@ Http::get('/v1/functions/:functionId/executions/:executionId')
->inject('response')
->inject('dbForProject')
->inject('mode')
- ->inject('authorization')
- ->action(function (string $functionId, string $executionId, Response $response, Database $dbForProject, string $mode, Authorization $authorization) {
- $function = $authorization->skip(fn () => $dbForProject->getDocument('functions', $functionId));
+ ->action(function (string $functionId, string $executionId, Response $response, Database $dbForProject, string $mode) {
+ $function = Authorization::skip(fn () => $dbForProject->getDocument('functions', $functionId));
- $isAPIKey = Auth::isAppUser($authorization->getRoles());
- $isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles());
+ $isAPIKey = Auth::isAppUser(Authorization::getRoles());
+ $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
if ($function->isEmpty() || (!$function->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) {
throw new Exception(Exception::FUNCTION_NOT_FOUND);
@@ -2233,7 +2222,7 @@ Http::get('/v1/functions/:functionId/executions/:executionId')
throw new Exception(Exception::EXECUTION_NOT_FOUND);
}
- $roles = $authorization->getRoles();
+ $roles = Authorization::getRoles();
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
$isAppUser = Auth::isAppUser($roles);
if (!$isPrivilegedUser && !$isAppUser) {
@@ -2244,7 +2233,7 @@ Http::get('/v1/functions/:functionId/executions/:executionId')
$response->dynamic($execution, Response::MODEL_EXECUTION);
});
-Http::delete('/v1/functions/:functionId/executions/:executionId')
+App::delete('/v1/functions/:functionId/executions/:executionId')
->groups(['api', 'functions'])
->desc('Delete execution')
->label('scope', 'execution.write')
@@ -2263,8 +2252,7 @@ Http::delete('/v1/functions/:functionId/executions/:executionId')
->inject('dbForProject')
->inject('dbForConsole')
->inject('queueForEvents')
- ->inject('authorization')
- ->action(function (string $functionId, string $executionId, Response $response, Database $dbForProject, Database $dbForConsole, Event $queueForEvents, Authorization $authorization) {
+ ->action(function (string $functionId, string $executionId, Response $response, Database $dbForProject, Database $dbForConsole, Event $queueForEvents) {
$function = $dbForProject->getDocument('functions', $functionId);
if ($function->isEmpty()) {
@@ -2301,7 +2289,7 @@ Http::delete('/v1/functions/:functionId/executions/:executionId')
->setAttribute('resourceUpdatedAt', DateTime::now())
->setAttribute('active', false);
- $authorization->skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule));
+ Authorization::skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule));
}
}
@@ -2315,7 +2303,7 @@ Http::delete('/v1/functions/:functionId/executions/:executionId')
// Variables
-Http::post('/v1/functions/:functionId/variables')
+App::post('/v1/functions/:functionId/variables')
->desc('Create variable')
->groups(['api', 'functions'])
->label('scope', 'functions.write')
@@ -2334,8 +2322,7 @@ Http::post('/v1/functions/:functionId/variables')
->inject('response')
->inject('dbForProject')
->inject('dbForConsole')
- ->inject('authorization')
- ->action(function (string $functionId, string $key, string $value, Response $response, Database $dbForProject, Database $dbForConsole, Authorization $authorization) {
+ ->action(function (string $functionId, string $key, string $value, Response $response, Database $dbForProject, Database $dbForConsole) {
$function = $dbForProject->getDocument('functions', $functionId);
if ($function->isEmpty()) {
@@ -2373,14 +2360,14 @@ Http::post('/v1/functions/:functionId/variables')
->setAttribute('resourceUpdatedAt', DateTime::now())
->setAttribute('schedule', $function->getAttribute('schedule'))
->setAttribute('active', !empty($function->getAttribute('schedule')) && !empty($function->getAttribute('deployment')));
- $authorization->skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule));
+ Authorization::skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule));
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($variable, Response::MODEL_VARIABLE);
});
-Http::get('/v1/functions/:functionId/variables')
+App::get('/v1/functions/:functionId/variables')
->desc('List variables')
->groups(['api', 'functions'])
->label('scope', 'functions.read')
@@ -2407,7 +2394,7 @@ Http::get('/v1/functions/:functionId/variables')
]), Response::MODEL_VARIABLE_LIST);
});
-Http::get('/v1/functions/:functionId/variables/:variableId')
+App::get('/v1/functions/:functionId/variables/:variableId')
->desc('Get variable')
->groups(['api', 'functions'])
->label('scope', 'functions.read')
@@ -2446,7 +2433,7 @@ Http::get('/v1/functions/:functionId/variables/:variableId')
$response->dynamic($variable, Response::MODEL_VARIABLE);
});
-Http::put('/v1/functions/:functionId/variables/:variableId')
+App::put('/v1/functions/:functionId/variables/:variableId')
->desc('Update variable')
->groups(['api', 'functions'])
->label('scope', 'functions.write')
@@ -2466,8 +2453,7 @@ Http::put('/v1/functions/:functionId/variables/:variableId')
->inject('response')
->inject('dbForProject')
->inject('dbForConsole')
- ->inject('authorization')
- ->action(function (string $functionId, string $variableId, string $key, ?string $value, Response $response, Database $dbForProject, Database $dbForConsole, Authorization $authorization) {
+ ->action(function (string $functionId, string $variableId, string $key, ?string $value, Response $response, Database $dbForProject, Database $dbForConsole) {
$function = $dbForProject->getDocument('functions', $functionId);
@@ -2503,12 +2489,12 @@ Http::put('/v1/functions/:functionId/variables/:variableId')
->setAttribute('resourceUpdatedAt', DateTime::now())
->setAttribute('schedule', $function->getAttribute('schedule'))
->setAttribute('active', !empty($function->getAttribute('schedule')) && !empty($function->getAttribute('deployment')));
- $authorization->skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule));
+ Authorization::skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule));
$response->dynamic($variable, Response::MODEL_VARIABLE);
});
-Http::delete('/v1/functions/:functionId/variables/:variableId')
+App::delete('/v1/functions/:functionId/variables/:variableId')
->desc('Delete variable')
->groups(['api', 'functions'])
->label('scope', 'functions.write')
@@ -2525,8 +2511,7 @@ Http::delete('/v1/functions/:functionId/variables/:variableId')
->inject('response')
->inject('dbForProject')
->inject('dbForConsole')
- ->inject('authorization')
- ->action(function (string $functionId, string $variableId, Response $response, Database $dbForProject, Database $dbForConsole, Authorization $authorization) {
+ ->action(function (string $functionId, string $variableId, Response $response, Database $dbForProject, Database $dbForConsole) {
$function = $dbForProject->getDocument('functions', $functionId);
if ($function->isEmpty()) {
@@ -2552,12 +2537,12 @@ Http::delete('/v1/functions/:functionId/variables/:variableId')
->setAttribute('resourceUpdatedAt', DateTime::now())
->setAttribute('schedule', $function->getAttribute('schedule'))
->setAttribute('active', !empty($function->getAttribute('schedule')) && !empty($function->getAttribute('deployment')));
- $authorization->skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule));
+ Authorization::skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule));
$response->noContent();
});
-Http::get('/v1/functions/templates')
+App::get('/v1/functions/templates')
->groups(['api'])
->desc('List function templates')
->label('scope', 'public')
@@ -2595,7 +2580,7 @@ Http::get('/v1/functions/templates')
]), Response::MODEL_TEMPLATE_FUNCTION_LIST);
});
-Http::get('/v1/functions/templates/:templateId')
+App::get('/v1/functions/templates/:templateId')
->desc('Get function template')
->label('scope', 'public')
->label('sdk.namespace', 'functions')
@@ -2610,10 +2595,9 @@ Http::get('/v1/functions/templates/:templateId')
->action(function (string $templateId, Response $response) {
$templates = Config::getParam('function-templates', []);
- $array = \array_filter($templates, function ($template) use ($templateId) {
+ $template = array_shift(\array_filter($templates, function ($template) use ($templateId) {
return $template['id'] === $templateId;
- });
- $template = array_shift($array);
+ }));
if (empty($template)) {
throw new Exception(Exception::FUNCTION_TEMPLATE_NOT_FOUND);
diff --git a/app/controllers/api/graphql.php b/app/controllers/api/graphql.php
index 0e7ddc783d..f79f433b5c 100644
--- a/app/controllers/api/graphql.php
+++ b/app/controllers/api/graphql.php
@@ -14,28 +14,27 @@ use GraphQL\Validator\Rules\DisableIntrospection;
use GraphQL\Validator\Rules\QueryComplexity;
use GraphQL\Validator\Rules\QueryDepth;
use Swoole\Coroutine\WaitGroup;
+use Utopia\App;
use Utopia\Database\Document;
use Utopia\Database\Validator\Authorization;
-use Utopia\Http\Http;
-use Utopia\Http\Validator\JSON;
-use Utopia\Http\Validator\Text;
use Utopia\System\System;
+use Utopia\Validator\JSON;
+use Utopia\Validator\Text;
-Http::init()
+App::init()
->groups(['graphql'])
->inject('project')
- ->inject('authorization')
- ->action(function (Document $project, Authorization $authorization) {
+ ->action(function (Document $project) {
if (
array_key_exists('graphql', $project->getAttribute('apis', []))
&& !$project->getAttribute('apis', [])['graphql']
- && !(Auth::isPrivilegedUser($authorization->getRoles()) || Auth::isAppUser($authorization->getRoles()))
+ && !(Auth::isPrivilegedUser(Authorization::getRoles()) || Auth::isAppUser(Authorization::getRoles()))
) {
throw new AppwriteException(AppwriteException::GENERAL_API_DISABLED);
}
});
-Http::get('/v1/graphql')
+App::get('/v1/graphql')
->desc('GraphQL endpoint')
->groups(['graphql'])
->label('scope', 'graphql')
@@ -75,7 +74,7 @@ Http::get('/v1/graphql')
->json($output);
});
-Http::post('/v1/graphql/mutation')
+App::post('/v1/graphql/mutation')
->desc('GraphQL endpoint')
->groups(['graphql'])
->label('scope', 'graphql')
@@ -120,7 +119,7 @@ Http::post('/v1/graphql/mutation')
->json($output);
});
-Http::post('/v1/graphql')
+App::post('/v1/graphql')
->desc('GraphQL endpoint')
->groups(['graphql'])
->label('scope', 'graphql')
@@ -157,6 +156,7 @@ Http::post('/v1/graphql')
if (\str_starts_with($type, 'multipart/form-data')) {
$query = parseMultipart($query, $request);
}
+
$output = execute($schema, $promiseAdapter, $query);
$response
@@ -205,7 +205,7 @@ function execute(
$validations[] = new QueryComplexity($maxComplexity);
$validations[] = new QueryDepth($maxDepth);
}
- if (Http::getMode() === Http::MODE_TYPE_PRODUCTION) {
+ if (App::getMode() === App::MODE_TYPE_PRODUCTION) {
$flags = DebugFlag::NONE;
}
@@ -306,10 +306,9 @@ function processResult($result, $debugFlags): array
);
}
-Http::shutdown()
+App::shutdown()
->groups(['schema'])
->inject('project')
- ->inject('schemaVariable')
- ->action(function (Document $project, Schema $schemaVariable) {
- $schemaVariable->setDirty($project->getId());
+ ->action(function (Document $project) {
+ Schema::setDirty($project->getId());
});
diff --git a/app/controllers/api/health.php b/app/controllers/api/health.php
index 47d80dbf71..f4581df8e4 100644
--- a/app/controllers/api/health.php
+++ b/app/controllers/api/health.php
@@ -3,19 +3,12 @@
use Appwrite\ClamAV\Network;
use Appwrite\Event\Event;
use Appwrite\Extend\Exception;
-use Appwrite\Utopia\Queue\Connections;
use Appwrite\Utopia\Response;
+use Utopia\App;
use Utopia\Config\Config;
-use Utopia\Database\Adapter\MariaDB;
-use Utopia\Database\Adapter\MySQL;
use Utopia\Database\Document;
use Utopia\Domains\Validator\PublicDomain;
-use Utopia\Http\Http;
-use Utopia\Http\Validator\Domain;
-use Utopia\Http\Validator\Integer;
-use Utopia\Http\Validator\Multiple;
-use Utopia\Http\Validator\Text;
-use Utopia\Http\Validator\WhiteList;
+use Utopia\Pools\Group;
use Utopia\Queue\Client;
use Utopia\Queue\Connection;
use Utopia\Registry\Registry;
@@ -23,8 +16,13 @@ use Utopia\Storage\Device;
use Utopia\Storage\Device\Local;
use Utopia\Storage\Storage;
use Utopia\System\System;
+use Utopia\Validator\Domain;
+use Utopia\Validator\Integer;
+use Utopia\Validator\Multiple;
+use Utopia\Validator\Text;
+use Utopia\Validator\WhiteList;
-Http::get('/v1/health')
+App::get('/v1/health')
->desc('Get HTTP')
->groups(['api', 'health'])
->label('scope', 'health.read')
@@ -47,7 +45,7 @@ Http::get('/v1/health')
$response->dynamic(new Document($output), Response::MODEL_HEALTH_STATUS);
});
-Http::get('/v1/health/version')
+App::get('/v1/health/version')
->desc('Get version')
->groups(['api', 'health'])
->label('scope', 'public')
@@ -59,7 +57,7 @@ Http::get('/v1/health/version')
$response->dynamic(new Document([ 'version' => APP_VERSION_STABLE ]), Response::MODEL_HEALTH_VERSION);
});
-Http::get('/v1/health/db')
+App::get('/v1/health/db')
->desc('Get DB')
->groups(['api', 'health'])
->label('scope', 'health.read')
@@ -72,34 +70,21 @@ Http::get('/v1/health/db')
->label('sdk.response.model', Response::MODEL_HEALTH_STATUS)
->inject('response')
->inject('pools')
- ->inject('connections')
- ->action(function (Response $response, array $pools, Connections $connections) {
+ ->action(function (Response $response, Group $pools) {
$output = [];
$configs = [
- 'console' => Config::getParam('pools-console'),
- 'database' => Config::getParam('pools-database'),
+ 'Console.DB' => Config::getParam('pools-console'),
+ 'Projects.DB' => Config::getParam('pools-database'),
];
foreach ($configs as $key => $config) {
foreach ($config as $database) {
- $checkStart = \microtime(true);
-
try {
+ $adapter = $pools->get($database)->pop()->getResource();
- $pool = $pools['pools-'.$key.'-'.$database]['pool'];
- $dsn = $pools['pools-'.$key.'-'.$database]['dsn'];
-
- $connection = $pool->get();
- $connections->add($connection, $pool);
- $adapter = match ($dsn->getScheme()) {
- 'mariadb' => new MariaDB($connection),
- 'mysql' => new MySQL($connection),
- default => null
- };
- $adapter->setDatabase($dsn->getPath());
-
+ $checkStart = \microtime(true);
if ($adapter->ping()) {
$output[] = new Document([
@@ -126,7 +111,7 @@ Http::get('/v1/health/db')
]), Response::MODEL_HEALTH_STATUS_LIST);
});
-Http::get('/v1/health/cache')
+App::get('/v1/health/cache')
->desc('Get cache')
->groups(['api', 'health'])
->label('scope', 'health.read')
@@ -139,8 +124,7 @@ Http::get('/v1/health/cache')
->label('sdk.response.model', Response::MODEL_HEALTH_STATUS)
->inject('response')
->inject('pools')
- ->inject('connections')
- ->action(function (Response $response, array $pools, Connections $connections) {
+ ->action(function (Response $response, Group $pools) {
$output = [];
@@ -150,142 +134,8 @@ Http::get('/v1/health/cache')
foreach ($configs as $key => $config) {
foreach ($config as $database) {
- $checkStart = \microtime(true);
try {
- $pool = $pools['pools-cache-' . $database]['pool'];
- $dsn = $pools['pools-cache-' . $database]['dsn'];
- $connection = $pool->get();
- $connections->add($connection, $pool);
-
- $adapter = new Connection\Redis($dsn->getHost(), $dsn->getPort());
-
-
- if ($adapter->ping()) {
- $output[] = new Document([
- 'name' => $key . " ($database)",
- 'status' => 'pass',
- 'ping' => \round((\microtime(true) - $checkStart) / 1000)
- ]);
- } else {
- $output[] = new Document([
- 'name' => $key . " ($database)",
- 'status' => 'fail',
- 'ping' => \round((\microtime(true) - $checkStart) / 1000)
- ]);
- }
- } catch (\Throwable $th) {
- $output[] = new Document([
- 'name' => $key . " ($database)",
- 'status' => 'fail',
- 'ping' => \round((\microtime(true) - $checkStart) / 1000)
- ]);
- } finally {
- $connections->reclaim();
- }
- }
- }
-
- $response->dynamic(new Document([
- 'statuses' => $output,
- 'total' => count($output),
- ]), Response::MODEL_HEALTH_STATUS_LIST);
- });
-
-Http::get('/v1/health/queue')
- ->desc('Get queue')
- ->groups(['api', 'health'])
- ->label('scope', 'health.read')
- ->label('sdk.auth', [APP_AUTH_TYPE_KEY])
- ->label('sdk.namespace', 'health')
- ->label('sdk.method', 'getQueue')
- ->label('sdk.description', '/docs/references/health/get-queue.md')
- ->label('sdk.response.code', Response::STATUS_CODE_OK)
- ->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
- ->label('sdk.response.model', Response::MODEL_HEALTH_STATUS)
- ->inject('response')
- ->inject('pools')
- ->inject('connections')
- ->action(function (Response $response, array $pools, Connections $connections) {
-
- $output = [];
-
- $configs = [
- 'Queue' => Config::getParam('pools-queue'),
- ];
-
- foreach ($configs as $key => $config) {
- $checkStart = \microtime(true);
-
- foreach ($config as $database) {
- try {
- $pool = $pools['pools-queue-' . $database]['pool'];
- $dsn = $pools['pools-queue-' . $database]['dsn'];
- $connection = $pool->get();
- $connections->add($connection, $pool);
-
- $adapter = new Connection\Redis($dsn->getHost(), $dsn->getPort());
- if ($adapter->ping()) {
- $output[] = new Document([
- 'name' => $key . " ($database)",
- 'status' => 'pass',
- 'ping' => \round((\microtime(true) - $checkStart) / 1000)
- ]);
- } else {
- $output[] = new Document([
- 'name' => $key . " ($database)",
- 'status' => 'fail',
- 'ping' => \round((\microtime(true) - $checkStart) / 1000)
- ]);
- }
- } catch (\Throwable $th) {
- $output[] = new Document([
- 'name' => $key . " ($database)",
- 'status' => 'fail',
- 'ping' => \round((\microtime(true) - $checkStart) / 1000)
- ]);
- } finally {
- $connections->reclaim();
- }
- }
- }
-
- $response->dynamic(new Document([
- 'statuses' => $output,
- 'total' => count($output),
- ]), Response::MODEL_HEALTH_STATUS_LIST);
- });
-
-Http::get('/v1/health/pubsub')
- ->desc('Get pubsub')
- ->groups(['api', 'health'])
- ->label('scope', 'health.read')
- ->label('sdk.auth', [APP_AUTH_TYPE_KEY])
- ->label('sdk.namespace', 'health')
- ->label('sdk.method', 'getPubSub')
- ->label('sdk.description', '/docs/references/health/get-pubsub.md')
- ->label('sdk.response.code', Response::STATUS_CODE_OK)
- ->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
- ->label('sdk.response.model', Response::MODEL_HEALTH_STATUS)
- ->inject('response')
- ->inject('pools')
- ->inject('connections')
- ->action(function (Response $response, array $pools, Connections $connections) {
-
- $output = [];
-
- $configs = [
- 'PubSub' => Config::getParam('pools-pubsub'),
- ];
-
- foreach ($configs as $key => $config) {
- foreach ($config as $database) {
- try {
- $pool = $pools['pools-pubsub-' . $database]['pool'];
-
- $connection = $pool->get();
- $connections->add($connection, $pool);
-
- $adapter = new Connection\Redis($connection);
+ $adapter = $pools->get($database)->pop()->getResource();
$checkStart = \microtime(true);
@@ -308,8 +158,6 @@ Http::get('/v1/health/pubsub')
'status' => 'fail',
'ping' => \round((\microtime(true) - $checkStart) / 1000)
]);
- } finally {
- $connections->reclaim();
}
}
}
@@ -320,7 +168,121 @@ Http::get('/v1/health/pubsub')
]), Response::MODEL_HEALTH_STATUS_LIST);
});
-Http::get('/v1/health/time')
+App::get('/v1/health/queue')
+ ->desc('Get queue')
+ ->groups(['api', 'health'])
+ ->label('scope', 'health.read')
+ ->label('sdk.auth', [APP_AUTH_TYPE_KEY])
+ ->label('sdk.namespace', 'health')
+ ->label('sdk.method', 'getQueue')
+ ->label('sdk.description', '/docs/references/health/get-queue.md')
+ ->label('sdk.response.code', Response::STATUS_CODE_OK)
+ ->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
+ ->label('sdk.response.model', Response::MODEL_HEALTH_STATUS)
+ ->inject('response')
+ ->inject('pools')
+ ->action(function (Response $response, Group $pools) {
+
+ $output = [];
+
+ $configs = [
+ 'Queue' => Config::getParam('pools-queue'),
+ ];
+
+ foreach ($configs as $key => $config) {
+ foreach ($config as $database) {
+ try {
+ $adapter = $pools->get($database)->pop()->getResource();
+
+ $checkStart = \microtime(true);
+
+ if ($adapter->ping()) {
+ $output[] = new Document([
+ 'name' => $key . " ($database)",
+ 'status' => 'pass',
+ 'ping' => \round((\microtime(true) - $checkStart) / 1000)
+ ]);
+ } else {
+ $output[] = new Document([
+ 'name' => $key . " ($database)",
+ 'status' => 'fail',
+ 'ping' => \round((\microtime(true) - $checkStart) / 1000)
+ ]);
+ }
+ } catch (\Throwable $th) {
+ $output[] = new Document([
+ 'name' => $key . " ($database)",
+ 'status' => 'fail',
+ 'ping' => \round((\microtime(true) - $checkStart) / 1000)
+ ]);
+ }
+ }
+ }
+
+ $response->dynamic(new Document([
+ 'statuses' => $output,
+ 'total' => count($output),
+ ]), Response::MODEL_HEALTH_STATUS_LIST);
+ });
+
+App::get('/v1/health/pubsub')
+ ->desc('Get pubsub')
+ ->groups(['api', 'health'])
+ ->label('scope', 'health.read')
+ ->label('sdk.auth', [APP_AUTH_TYPE_KEY])
+ ->label('sdk.namespace', 'health')
+ ->label('sdk.method', 'getPubSub')
+ ->label('sdk.description', '/docs/references/health/get-pubsub.md')
+ ->label('sdk.response.code', Response::STATUS_CODE_OK)
+ ->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
+ ->label('sdk.response.model', Response::MODEL_HEALTH_STATUS)
+ ->inject('response')
+ ->inject('pools')
+ ->action(function (Response $response, Group $pools) {
+
+ $output = [];
+
+ $configs = [
+ 'PubSub' => Config::getParam('pools-pubsub'),
+ ];
+
+ foreach ($configs as $key => $config) {
+ foreach ($config as $database) {
+ try {
+ $adapter = $pools->get($database)->pop()->getResource();
+
+ $checkStart = \microtime(true);
+
+ if ($adapter->ping()) {
+ $output[] = new Document([
+ 'name' => $key . " ($database)",
+ 'status' => 'pass',
+ 'ping' => \round((\microtime(true) - $checkStart) / 1000)
+ ]);
+ } else {
+ $output[] = new Document([
+ 'name' => $key . " ($database)",
+ 'status' => 'fail',
+ 'ping' => \round((\microtime(true) - $checkStart) / 1000)
+ ]);
+ }
+ } catch (\Throwable $th) {
+ $output[] = new Document([
+ 'name' => $key . " ($database)",
+ 'status' => 'fail',
+ 'ping' => \round((\microtime(true) - $checkStart) / 1000)
+ ]);
+ }
+ }
+ }
+
+ $response->dynamic(new Document([
+ 'statuses' => $output,
+ 'total' => count($output),
+ ]), Response::MODEL_HEALTH_STATUS_LIST);
+ });
+
+App::get('/v1/health/time')
->desc('Get time')
->groups(['api', 'health'])
->label('scope', 'health.read')
@@ -377,7 +339,7 @@ Http::get('/v1/health/time')
$response->dynamic(new Document($output), Response::MODEL_HEALTH_TIME);
});
-Http::get('/v1/health/queue/webhooks')
+App::get('/v1/health/queue/webhooks')
->desc('Get webhooks queue')
->groups(['api', 'health'])
->label('scope', 'health.read')
@@ -404,7 +366,7 @@ Http::get('/v1/health/queue/webhooks')
$response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE);
}, ['response']);
-Http::get('/v1/health/queue/logs')
+App::get('/v1/health/queue/logs')
->desc('Get logs queue')
->groups(['api', 'health'])
->label('scope', 'health.read')
@@ -431,7 +393,7 @@ Http::get('/v1/health/queue/logs')
$response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE);
}, ['response']);
-Http::get('/v1/health/certificate')
+App::get('/v1/health/certificate')
->desc('Get the SSL certificate for a domain')
->groups(['api', 'health'])
->label('scope', 'health.read')
@@ -481,7 +443,7 @@ Http::get('/v1/health/certificate')
]), Response::MODEL_HEALTH_CERTIFICATE);
}, ['response']);
-Http::get('/v1/health/queue/certificates')
+App::get('/v1/health/queue/certificates')
->desc('Get certificates queue')
->groups(['api', 'health'])
->label('scope', 'health.read')
@@ -508,7 +470,7 @@ Http::get('/v1/health/queue/certificates')
$response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE);
}, ['response']);
-Http::get('/v1/health/queue/builds')
+App::get('/v1/health/queue/builds')
->desc('Get builds queue')
->groups(['api', 'health'])
->label('scope', 'health.read')
@@ -535,7 +497,7 @@ Http::get('/v1/health/queue/builds')
$response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE);
}, ['response']);
-Http::get('/v1/health/queue/databases')
+App::get('/v1/health/queue/databases')
->desc('Get databases queue')
->groups(['api', 'health'])
->label('scope', 'health.read')
@@ -563,7 +525,7 @@ Http::get('/v1/health/queue/databases')
$response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE);
}, ['response']);
-Http::get('/v1/health/queue/deletes')
+App::get('/v1/health/queue/deletes')
->desc('Get deletes queue')
->groups(['api', 'health'])
->label('scope', 'health.read')
@@ -590,7 +552,7 @@ Http::get('/v1/health/queue/deletes')
$response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE);
}, ['response']);
-Http::get('/v1/health/queue/mails')
+App::get('/v1/health/queue/mails')
->desc('Get mails queue')
->groups(['api', 'health'])
->label('scope', 'health.read')
@@ -617,7 +579,7 @@ Http::get('/v1/health/queue/mails')
$response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE);
}, ['response']);
-Http::get('/v1/health/queue/messaging')
+App::get('/v1/health/queue/messaging')
->desc('Get messaging queue')
->groups(['api', 'health'])
->label('scope', 'health.read')
@@ -644,7 +606,7 @@ Http::get('/v1/health/queue/messaging')
$response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE);
}, ['response']);
-Http::get('/v1/health/queue/migrations')
+App::get('/v1/health/queue/migrations')
->desc('Get migrations queue')
->groups(['api', 'health'])
->label('scope', 'health.read')
@@ -671,7 +633,7 @@ Http::get('/v1/health/queue/migrations')
$response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE);
}, ['response']);
-Http::get('/v1/health/queue/functions')
+App::get('/v1/health/queue/functions')
->desc('Get functions queue')
->groups(['api', 'health'])
->label('scope', 'health.read')
@@ -698,7 +660,7 @@ Http::get('/v1/health/queue/functions')
$response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE);
}, ['response']);
-Http::get('/v1/health/queue/usage')
+App::get('/v1/health/queue/usage')
->desc('Get usage queue')
->groups(['api', 'health'])
->label('scope', 'health.read')
@@ -725,7 +687,7 @@ Http::get('/v1/health/queue/usage')
$response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE);
});
-Http::get('/v1/health/queue/usage-dump')
+App::get('/v1/health/queue/usage-dump')
->desc('Get usage dump queue')
->groups(['api', 'health'])
->label('scope', 'health.read')
@@ -752,7 +714,7 @@ Http::get('/v1/health/queue/usage-dump')
$response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE);
});
-Http::get('/v1/health/storage/local')
+App::get('/v1/health/storage/local')
->desc('Get local storage')
->groups(['api', 'health'])
->label('scope', 'health.read')
@@ -795,7 +757,7 @@ Http::get('/v1/health/storage/local')
$response->dynamic(new Document($output), Response::MODEL_HEALTH_STATUS);
});
-Http::get('/v1/health/storage')
+App::get('/v1/health/storage')
->desc('Get storage')
->groups(['api', 'health'])
->label('scope', 'health.read')
@@ -836,7 +798,7 @@ Http::get('/v1/health/storage')
$response->dynamic(new Document($output), Response::MODEL_HEALTH_STATUS);
});
-Http::get('/v1/health/anti-virus')
+App::get('/v1/health/anti-virus')
->desc('Get antivirus')
->groups(['api', 'health'])
->label('scope', 'health.read')
@@ -875,7 +837,7 @@ Http::get('/v1/health/anti-virus')
$response->dynamic(new Document($output), Response::MODEL_HEALTH_ANTIVIRUS);
});
-Http::get('/v1/health/queue/failed/:name')
+App::get('/v1/health/queue/failed/:name')
->desc('Get number of failed queue jobs')
->groups(['api', 'health'])
->label('scope', 'health.read')
@@ -916,7 +878,7 @@ Http::get('/v1/health/queue/failed/:name')
$response->dynamic(new Document([ 'size' => $failed ]), Response::MODEL_HEALTH_QUEUE);
});
-Http::get('/v1/health/stats') // Currently only used internally
+App::get('/v1/health/stats') // Currently only used internally
->desc('Get system stats')
->groups(['api', 'health'])
->label('scope', 'root')
diff --git a/app/controllers/api/locale.php b/app/controllers/api/locale.php
index fcaf0c03cb..2917bc8416 100644
--- a/app/controllers/api/locale.php
+++ b/app/controllers/api/locale.php
@@ -3,12 +3,12 @@
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response;
use MaxMind\Db\Reader;
+use Utopia\App;
use Utopia\Config\Config;
use Utopia\Database\Document;
-use Utopia\Http\Http;
use Utopia\Locale\Locale;
-Http::get('/v1/locale')
+App::get('/v1/locale')
->desc('Get user locale')
->groups(['api', 'locale'])
->label('scope', 'locale.read')
@@ -68,8 +68,8 @@ Http::get('/v1/locale')
$response->dynamic(new Document($output), Response::MODEL_LOCALE);
});
-Http::get('/v1/locale/codes')
- ->desc('List Locale Codes')
+App::get('/v1/locale/codes')
+ ->desc('List locale codes')
->groups(['api', 'locale'])
->label('scope', 'locale.read')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
@@ -90,7 +90,7 @@ Http::get('/v1/locale/codes')
]), Response::MODEL_LOCALE_CODE_LIST);
});
-Http::get('/v1/locale/countries')
+App::get('/v1/locale/countries')
->desc('List countries')
->groups(['api', 'locale'])
->label('scope', 'locale.read')
@@ -123,7 +123,7 @@ Http::get('/v1/locale/countries')
$response->dynamic(new Document(['countries' => $output, 'total' => \count($output)]), Response::MODEL_COUNTRY_LIST);
});
-Http::get('/v1/locale/countries/eu')
+App::get('/v1/locale/countries/eu')
->desc('List EU countries')
->groups(['api', 'locale'])
->label('scope', 'locale.read')
@@ -158,7 +158,7 @@ Http::get('/v1/locale/countries/eu')
$response->dynamic(new Document(['countries' => $output, 'total' => \count($output)]), Response::MODEL_COUNTRY_LIST);
});
-Http::get('/v1/locale/countries/phones')
+App::get('/v1/locale/countries/phones')
->desc('List countries phone codes')
->groups(['api', 'locale'])
->label('scope', 'locale.read')
@@ -192,7 +192,7 @@ Http::get('/v1/locale/countries/phones')
$response->dynamic(new Document(['phones' => $output, 'total' => \count($output)]), Response::MODEL_PHONE_LIST);
});
-Http::get('/v1/locale/continents')
+App::get('/v1/locale/continents')
->desc('List continents')
->groups(['api', 'locale'])
->label('scope', 'locale.read')
@@ -224,7 +224,7 @@ Http::get('/v1/locale/continents')
$response->dynamic(new Document(['continents' => $output, 'total' => \count($output)]), Response::MODEL_CONTINENT_LIST);
});
-Http::get('/v1/locale/currencies')
+App::get('/v1/locale/currencies')
->desc('List currencies')
->groups(['api', 'locale'])
->label('scope', 'locale.read')
@@ -247,7 +247,7 @@ Http::get('/v1/locale/currencies')
});
-Http::get('/v1/locale/languages')
+App::get('/v1/locale/languages')
->desc('List languages')
->groups(['api', 'locale'])
->label('scope', 'locale.read')
diff --git a/app/controllers/api/messaging.php b/app/controllers/api/messaging.php
index 8beb38c7ca..7da0348a8f 100644
--- a/app/controllers/api/messaging.php
+++ b/app/controllers/api/messaging.php
@@ -20,6 +20,7 @@ use Appwrite\Utopia\Database\Validator\Queries\Targets;
use Appwrite\Utopia\Database\Validator\Queries\Topics;
use Appwrite\Utopia\Response;
use MaxMind\Db\Reader;
+use Utopia\App;
use Utopia\Audit\Audit;
use Utopia\Database\Database;
use Utopia\Database\DateTime;
@@ -29,27 +30,25 @@ use Utopia\Database\Exception\Query as QueryException;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
-use Utopia\Database\Validator\Authorization\Input;
use Utopia\Database\Validator\Datetime as DatetimeValidator;
use Utopia\Database\Validator\Queries;
use Utopia\Database\Validator\Query\Limit;
use Utopia\Database\Validator\Query\Offset;
use Utopia\Database\Validator\Roles;
use Utopia\Database\Validator\UID;
-use Utopia\Http\Http;
-use Utopia\Http\Validator\ArrayList;
-use Utopia\Http\Validator\Boolean;
-use Utopia\Http\Validator\Integer;
-use Utopia\Http\Validator\JSON;
-use Utopia\Http\Validator\Range;
-use Utopia\Http\Validator\Text;
-use Utopia\Http\Validator\WhiteList;
use Utopia\Locale\Locale;
use Utopia\System\System;
+use Utopia\Validator\ArrayList;
+use Utopia\Validator\Boolean;
+use Utopia\Validator\Integer;
+use Utopia\Validator\JSON;
+use Utopia\Validator\Range;
+use Utopia\Validator\Text;
+use Utopia\Validator\WhiteList;
use function Swoole\Coroutine\batch;
-Http::post('/v1/messaging/providers/mailgun')
+App::post('/v1/messaging/providers/mailgun')
->desc('Create Mailgun provider')
->groups(['api', 'messaging'])
->label('audits.event', 'provider.create')
@@ -136,7 +135,7 @@ Http::post('/v1/messaging/providers/mailgun')
->dynamic($provider, Response::MODEL_PROVIDER);
});
-Http::post('/v1/messaging/providers/sendgrid')
+App::post('/v1/messaging/providers/sendgrid')
->desc('Create Sendgrid provider')
->groups(['api', 'messaging'])
->label('audits.event', 'provider.create')
@@ -211,7 +210,7 @@ Http::post('/v1/messaging/providers/sendgrid')
->dynamic($provider, Response::MODEL_PROVIDER);
});
-Http::post('/v1/messaging/providers/smtp')
+App::post('/v1/messaging/providers/smtp')
->desc('Create SMTP provider')
->groups(['api', 'messaging'])
->label('audits.event', 'provider.create')
@@ -299,7 +298,7 @@ Http::post('/v1/messaging/providers/smtp')
->dynamic($provider, Response::MODEL_PROVIDER);
});
-Http::post('/v1/messaging/providers/msg91')
+App::post('/v1/messaging/providers/msg91')
->desc('Create Msg91 provider')
->groups(['api', 'messaging'])
->label('audits.event', 'provider.create')
@@ -375,7 +374,7 @@ Http::post('/v1/messaging/providers/msg91')
->dynamic($provider, Response::MODEL_PROVIDER);
});
-Http::post('/v1/messaging/providers/telesign')
+App::post('/v1/messaging/providers/telesign')
->desc('Create Telesign provider')
->groups(['api', 'messaging'])
->label('audits.event', 'provider.create')
@@ -452,7 +451,7 @@ Http::post('/v1/messaging/providers/telesign')
->dynamic($provider, Response::MODEL_PROVIDER);
});
-Http::post('/v1/messaging/providers/textmagic')
+App::post('/v1/messaging/providers/textmagic')
->desc('Create Textmagic provider')
->groups(['api', 'messaging'])
->label('audits.event', 'provider.create')
@@ -529,7 +528,7 @@ Http::post('/v1/messaging/providers/textmagic')
->dynamic($provider, Response::MODEL_PROVIDER);
});
-Http::post('/v1/messaging/providers/twilio')
+App::post('/v1/messaging/providers/twilio')
->desc('Create Twilio provider')
->groups(['api', 'messaging'])
->label('audits.event', 'provider.create')
@@ -606,7 +605,7 @@ Http::post('/v1/messaging/providers/twilio')
->dynamic($provider, Response::MODEL_PROVIDER);
});
-Http::post('/v1/messaging/providers/vonage')
+App::post('/v1/messaging/providers/vonage')
->desc('Create Vonage provider')
->groups(['api', 'messaging'])
->label('audits.event', 'provider.create')
@@ -683,7 +682,7 @@ Http::post('/v1/messaging/providers/vonage')
->dynamic($provider, Response::MODEL_PROVIDER);
});
-Http::post('/v1/messaging/providers/fcm')
+App::post('/v1/messaging/providers/fcm')
->desc('Create FCM provider')
->groups(['api', 'messaging'])
->label('audits.event', 'provider.create')
@@ -746,7 +745,7 @@ Http::post('/v1/messaging/providers/fcm')
->dynamic($provider, Response::MODEL_PROVIDER);
});
-Http::post('/v1/messaging/providers/apns')
+App::post('/v1/messaging/providers/apns')
->desc('Create APNS provider')
->groups(['api', 'messaging'])
->label('audits.event', 'provider.create')
@@ -832,7 +831,7 @@ Http::post('/v1/messaging/providers/apns')
->dynamic($provider, Response::MODEL_PROVIDER);
});
-Http::get('/v1/messaging/providers')
+App::get('/v1/messaging/providers')
->desc('List providers')
->groups(['api', 'messaging'])
->label('scope', 'providers.read')
@@ -847,8 +846,7 @@ Http::get('/v1/messaging/providers')
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
->inject('dbForProject')
->inject('response')
- ->inject('authorization')
- ->action(function (array $queries, string $search, Database $dbForProject, Response $response, Authorization $authorization) {
+ ->action(function (array $queries, string $search, Database $dbForProject, Response $response) {
try {
$queries = Query::parseQueries($queries);
} catch (QueryException $e) {
@@ -869,7 +867,7 @@ Http::get('/v1/messaging/providers')
if ($cursor) {
$providerId = $cursor->getValue();
- $cursorDocument = $authorization->skip(fn () => $dbForProject->getDocument('providers', $providerId));
+ $cursorDocument = Authorization::skip(fn () => $dbForProject->getDocument('providers', $providerId));
if ($cursorDocument->isEmpty()) {
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Provider '{$providerId}' for the 'cursor' value not found.");
@@ -884,7 +882,7 @@ Http::get('/v1/messaging/providers')
]), Response::MODEL_PROVIDER_LIST);
});
-Http::get('/v1/messaging/providers/:providerId/logs')
+App::get('/v1/messaging/providers/:providerId/logs')
->desc('List provider logs')
->groups(['api', 'messaging'])
->label('scope', 'providers.read')
@@ -972,7 +970,7 @@ Http::get('/v1/messaging/providers/:providerId/logs')
]), Response::MODEL_LOG_LIST);
});
-Http::get('/v1/messaging/providers/:providerId')
+App::get('/v1/messaging/providers/:providerId')
->desc('Get provider')
->groups(['api', 'messaging'])
->label('scope', 'providers.read')
@@ -996,7 +994,7 @@ Http::get('/v1/messaging/providers/:providerId')
$response->dynamic($provider, Response::MODEL_PROVIDER);
});
-Http::patch('/v1/messaging/providers/mailgun/:providerId')
+App::patch('/v1/messaging/providers/mailgun/:providerId')
->desc('Update Mailgun provider')
->groups(['api', 'messaging'])
->label('audits.event', 'provider.update')
@@ -1102,7 +1100,7 @@ Http::patch('/v1/messaging/providers/mailgun/:providerId')
->dynamic($provider, Response::MODEL_PROVIDER);
});
-Http::patch('/v1/messaging/providers/sendgrid/:providerId')
+App::patch('/v1/messaging/providers/sendgrid/:providerId')
->desc('Update Sendgrid provider')
->groups(['api', 'messaging'])
->label('audits.event', 'provider.update')
@@ -1193,7 +1191,7 @@ Http::patch('/v1/messaging/providers/sendgrid/:providerId')
->dynamic($provider, Response::MODEL_PROVIDER);
});
-Http::patch('/v1/messaging/providers/smtp/:providerId')
+App::patch('/v1/messaging/providers/smtp/:providerId')
->desc('Update SMTP provider')
->groups(['api', 'messaging'])
->label('audits.event', 'provider.update')
@@ -1315,7 +1313,7 @@ Http::patch('/v1/messaging/providers/smtp/:providerId')
->dynamic($provider, Response::MODEL_PROVIDER);
});
-Http::patch('/v1/messaging/providers/msg91/:providerId')
+App::patch('/v1/messaging/providers/msg91/:providerId')
->desc('Update Msg91 provider')
->groups(['api', 'messaging'])
->label('audits.event', 'provider.update')
@@ -1395,7 +1393,7 @@ Http::patch('/v1/messaging/providers/msg91/:providerId')
->dynamic($provider, Response::MODEL_PROVIDER);
});
-Http::patch('/v1/messaging/providers/telesign/:providerId')
+App::patch('/v1/messaging/providers/telesign/:providerId')
->desc('Update Telesign provider')
->groups(['api', 'messaging'])
->label('audits.event', 'provider.update')
@@ -1477,7 +1475,7 @@ Http::patch('/v1/messaging/providers/telesign/:providerId')
->dynamic($provider, Response::MODEL_PROVIDER);
});
-Http::patch('/v1/messaging/providers/textmagic/:providerId')
+App::patch('/v1/messaging/providers/textmagic/:providerId')
->desc('Update Textmagic provider')
->groups(['api', 'messaging'])
->label('audits.event', 'provider.update')
@@ -1559,7 +1557,7 @@ Http::patch('/v1/messaging/providers/textmagic/:providerId')
->dynamic($provider, Response::MODEL_PROVIDER);
});
-Http::patch('/v1/messaging/providers/twilio/:providerId')
+App::patch('/v1/messaging/providers/twilio/:providerId')
->desc('Update Twilio provider')
->groups(['api', 'messaging'])
->label('audits.event', 'provider.update')
@@ -1641,7 +1639,7 @@ Http::patch('/v1/messaging/providers/twilio/:providerId')
->dynamic($provider, Response::MODEL_PROVIDER);
});
-Http::patch('/v1/messaging/providers/vonage/:providerId')
+App::patch('/v1/messaging/providers/vonage/:providerId')
->desc('Update Vonage provider')
->groups(['api', 'messaging'])
->label('audits.event', 'provider.update')
@@ -1723,7 +1721,7 @@ Http::patch('/v1/messaging/providers/vonage/:providerId')
->dynamic($provider, Response::MODEL_PROVIDER);
});
-Http::patch('/v1/messaging/providers/fcm/:providerId')
+App::patch('/v1/messaging/providers/fcm/:providerId')
->desc('Update FCM provider')
->groups(['api', 'messaging'])
->label('audits.event', 'provider.update')
@@ -1792,7 +1790,7 @@ Http::patch('/v1/messaging/providers/fcm/:providerId')
});
-Http::patch('/v1/messaging/providers/apns/:providerId')
+App::patch('/v1/messaging/providers/apns/:providerId')
->desc('Update APNS provider')
->groups(['api', 'messaging'])
->label('audits.event', 'provider.update')
@@ -1887,7 +1885,7 @@ Http::patch('/v1/messaging/providers/apns/:providerId')
->dynamic($provider, Response::MODEL_PROVIDER);
});
-Http::delete('/v1/messaging/providers/:providerId')
+App::delete('/v1/messaging/providers/:providerId')
->desc('Delete provider')
->groups(['api', 'messaging'])
->label('audits.event', 'provider.delete')
@@ -1922,7 +1920,7 @@ Http::delete('/v1/messaging/providers/:providerId')
->noContent();
});
-Http::post('/v1/messaging/topics')
+App::post('/v1/messaging/topics')
->desc('Create topic')
->groups(['api', 'messaging'])
->label('audits.event', 'topic.create')
@@ -1965,7 +1963,7 @@ Http::post('/v1/messaging/topics')
->dynamic($topic, Response::MODEL_TOPIC);
});
-Http::get('/v1/messaging/topics')
+App::get('/v1/messaging/topics')
->desc('List topics')
->groups(['api', 'messaging'])
->label('scope', 'topics.read')
@@ -1980,8 +1978,7 @@ Http::get('/v1/messaging/topics')
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
->inject('dbForProject')
->inject('response')
- ->inject('authorization')
- ->action(function (array $queries, string $search, Database $dbForProject, Response $response, Authorization $authorization) {
+ ->action(function (array $queries, string $search, Database $dbForProject, Response $response) {
try {
$queries = Query::parseQueries($queries);
} catch (QueryException $e) {
@@ -2002,7 +1999,7 @@ Http::get('/v1/messaging/topics')
if ($cursor) {
$topicId = $cursor->getValue();
- $cursorDocument = $authorization->skip(fn () => $dbForProject->getDocument('topics', $topicId));
+ $cursorDocument = Authorization::skip(fn () => $dbForProject->getDocument('topics', $topicId));
if ($cursorDocument->isEmpty()) {
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Topic '{$topicId}' for the 'cursor' value not found.");
@@ -2017,7 +2014,7 @@ Http::get('/v1/messaging/topics')
]), Response::MODEL_TOPIC_LIST);
});
-Http::get('/v1/messaging/topics/:topicId/logs')
+App::get('/v1/messaging/topics/:topicId/logs')
->desc('List topic logs')
->groups(['api', 'messaging'])
->label('scope', 'topics.read')
@@ -2034,8 +2031,7 @@ Http::get('/v1/messaging/topics/:topicId/logs')
->inject('dbForProject')
->inject('locale')
->inject('geodb')
- ->inject('authorization')
- ->action(function (string $topicId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb, Authorization $authorization) {
+ ->action(function (string $topicId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) {
$topic = $dbForProject->getDocument('topics', $topicId);
if ($topic->isEmpty()) {
@@ -2107,7 +2103,7 @@ Http::get('/v1/messaging/topics/:topicId/logs')
]), Response::MODEL_LOG_LIST);
});
-Http::get('/v1/messaging/topics/:topicId')
+App::get('/v1/messaging/topics/:topicId')
->desc('Get topic')
->groups(['api', 'messaging'])
->label('scope', 'topics.read')
@@ -2132,7 +2128,7 @@ Http::get('/v1/messaging/topics/:topicId')
->dynamic($topic, Response::MODEL_TOPIC);
});
-Http::patch('/v1/messaging/topics/:topicId')
+App::patch('/v1/messaging/topics/:topicId')
->desc('Update topic')
->groups(['api', 'messaging'])
->label('audits.event', 'topic.update')
@@ -2176,7 +2172,7 @@ Http::patch('/v1/messaging/topics/:topicId')
->dynamic($topic, Response::MODEL_TOPIC);
});
-Http::delete('/v1/messaging/topics/:topicId')
+App::delete('/v1/messaging/topics/:topicId')
->desc('Delete topic')
->groups(['api', 'messaging'])
->label('audits.event', 'topic.delete')
@@ -2216,7 +2212,7 @@ Http::delete('/v1/messaging/topics/:topicId')
->noContent();
});
-Http::post('/v1/messaging/topics/:topicId/subscribers')
+App::post('/v1/messaging/topics/:topicId/subscribers')
->desc('Create subscriber')
->groups(['api', 'messaging'])
->label('audits.event', 'subscriber.create')
@@ -2236,27 +2232,28 @@ Http::post('/v1/messaging/topics/:topicId/subscribers')
->inject('queueForEvents')
->inject('dbForProject')
->inject('response')
- ->inject('authorization')
- ->action(function (string $subscriberId, string $topicId, string $targetId, Event $queueForEvents, Database $dbForProject, Response $response, Authorization $authorization) {
+ ->action(function (string $subscriberId, string $topicId, string $targetId, Event $queueForEvents, Database $dbForProject, Response $response) {
$subscriberId = $subscriberId == 'unique()' ? ID::unique() : $subscriberId;
- $topic = $authorization->skip(fn () => $dbForProject->getDocument('topics', $topicId));
+ $topic = Authorization::skip(fn () => $dbForProject->getDocument('topics', $topicId));
if ($topic->isEmpty()) {
throw new Exception(Exception::TOPIC_NOT_FOUND);
}
- if (!$authorization->isValid(new Input('subscribe', $topic->getAttribute('subscribe')))) {
- throw new Exception(Exception::USER_UNAUTHORIZED, $authorization->getDescription());
+ $validator = new Authorization('subscribe');
+
+ if (!$validator->isValid($topic->getAttribute('subscribe'))) {
+ throw new Exception(Exception::USER_UNAUTHORIZED, $validator->getDescription());
}
- $target = $authorization->skip(fn () => $dbForProject->getDocument('targets', $targetId));
+ $target = Authorization::skip(fn () => $dbForProject->getDocument('targets', $targetId));
if ($target->isEmpty()) {
throw new Exception(Exception::USER_TARGET_NOT_FOUND);
}
- $user = $authorization->skip(fn () => $dbForProject->getDocument('users', $target->getAttribute('userId')));
+ $user = Authorization::skip(fn () => $dbForProject->getDocument('users', $target->getAttribute('userId')));
$subscriber = new Document([
'$id' => $subscriberId,
@@ -2289,7 +2286,7 @@ Http::post('/v1/messaging/topics/:topicId/subscribers')
default => throw new Exception(Exception::TARGET_PROVIDER_INVALID_TYPE),
};
- $authorization->skip(fn () => $dbForProject->increaseDocumentAttribute(
+ Authorization::skip(fn () => $dbForProject->increaseDocumentAttribute(
'topics',
$topicId,
$totalAttribute,
@@ -2311,7 +2308,7 @@ Http::post('/v1/messaging/topics/:topicId/subscribers')
->dynamic($subscriber, Response::MODEL_SUBSCRIBER);
});
-Http::get('/v1/messaging/topics/:topicId/subscribers')
+App::get('/v1/messaging/topics/:topicId/subscribers')
->desc('List subscribers')
->groups(['api', 'messaging'])
->label('scope', 'subscribers.read')
@@ -2327,8 +2324,7 @@ Http::get('/v1/messaging/topics/:topicId/subscribers')
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
->inject('dbForProject')
->inject('response')
- ->inject('authorization')
- ->action(function (string $topicId, array $queries, string $search, Database $dbForProject, Response $response, Authorization $authorization) {
+ ->action(function (string $topicId, array $queries, string $search, Database $dbForProject, Response $response) {
try {
$queries = Query::parseQueries($queries);
} catch (QueryException $e) {
@@ -2339,7 +2335,7 @@ Http::get('/v1/messaging/topics/:topicId/subscribers')
$queries[] = Query::search('search', $search);
}
- $topic = $authorization->skip(fn () => $dbForProject->getDocument('topics', $topicId));
+ $topic = Authorization::skip(fn () => $dbForProject->getDocument('topics', $topicId));
if ($topic->isEmpty()) {
throw new Exception(Exception::TOPIC_NOT_FOUND);
@@ -2357,7 +2353,7 @@ Http::get('/v1/messaging/topics/:topicId/subscribers')
if ($cursor) {
$subscriberId = $cursor->getValue();
- $cursorDocument = $authorization->skip(fn () => $dbForProject->getDocument('subscribers', $subscriberId));
+ $cursorDocument = Authorization::skip(fn () => $dbForProject->getDocument('subscribers', $subscriberId));
if ($cursorDocument->isEmpty()) {
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Subscriber '{$subscriberId}' for the 'cursor' value not found.");
@@ -2368,10 +2364,10 @@ Http::get('/v1/messaging/topics/:topicId/subscribers')
$subscribers = $dbForProject->find('subscribers', $queries);
- $subscribers = batch(\array_map(function (Document $subscriber) use ($dbForProject, $authorization) {
- return function () use ($subscriber, $dbForProject, $authorization) {
- $target = $authorization->skip(fn () => $dbForProject->getDocument('targets', $subscriber->getAttribute('targetId')));
- $user = $authorization->skip(fn () => $dbForProject->getDocument('users', $target->getAttribute('userId')));
+ $subscribers = batch(\array_map(function (Document $subscriber) use ($dbForProject) {
+ return function () use ($subscriber, $dbForProject) {
+ $target = Authorization::skip(fn () => $dbForProject->getDocument('targets', $subscriber->getAttribute('targetId')));
+ $user = Authorization::skip(fn () => $dbForProject->getDocument('users', $target->getAttribute('userId')));
return $subscriber
->setAttribute('target', $target)
@@ -2386,7 +2382,7 @@ Http::get('/v1/messaging/topics/:topicId/subscribers')
]), Response::MODEL_SUBSCRIBER_LIST);
});
-Http::get('/v1/messaging/subscribers/:subscriberId/logs')
+App::get('/v1/messaging/subscribers/:subscriberId/logs')
->desc('List subscriber logs')
->groups(['api', 'messaging'])
->label('scope', 'subscribers.read')
@@ -2403,8 +2399,7 @@ Http::get('/v1/messaging/subscribers/:subscriberId/logs')
->inject('dbForProject')
->inject('locale')
->inject('geodb')
- ->inject('authorization')
- ->action(function (string $subscriberId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb, Authorization $authorization) {
+ ->action(function (string $subscriberId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) {
$subscriber = $dbForProject->getDocument('subscribers', $subscriberId);
if ($subscriber->isEmpty()) {
@@ -2476,7 +2471,7 @@ Http::get('/v1/messaging/subscribers/:subscriberId/logs')
]), Response::MODEL_LOG_LIST);
});
-Http::get('/v1/messaging/topics/:topicId/subscribers/:subscriberId')
+App::get('/v1/messaging/topics/:topicId/subscribers/:subscriberId')
->desc('Get subscriber')
->groups(['api', 'messaging'])
->label('scope', 'subscribers.read')
@@ -2491,9 +2486,8 @@ Http::get('/v1/messaging/topics/:topicId/subscribers/:subscriberId')
->param('subscriberId', '', new UID(), 'Subscriber ID.')
->inject('dbForProject')
->inject('response')
- ->inject('authorization')
- ->action(function (string $topicId, string $subscriberId, Database $dbForProject, Response $response, Authorization $authorization) {
- $topic = $authorization->skip(fn () => $dbForProject->getDocument('topics', $topicId));
+ ->action(function (string $topicId, string $subscriberId, Database $dbForProject, Response $response) {
+ $topic = Authorization::skip(fn () => $dbForProject->getDocument('topics', $topicId));
if ($topic->isEmpty()) {
throw new Exception(Exception::TOPIC_NOT_FOUND);
@@ -2505,8 +2499,8 @@ Http::get('/v1/messaging/topics/:topicId/subscribers/:subscriberId')
throw new Exception(Exception::SUBSCRIBER_NOT_FOUND);
}
- $target = $authorization->skip(fn () => $dbForProject->getDocument('targets', $subscriber->getAttribute('targetId')));
- $user = $authorization->skip(fn () => $dbForProject->getDocument('users', $target->getAttribute('userId')));
+ $target = Authorization::skip(fn () => $dbForProject->getDocument('targets', $subscriber->getAttribute('targetId')));
+ $user = Authorization::skip(fn () => $dbForProject->getDocument('users', $target->getAttribute('userId')));
$subscriber
->setAttribute('target', $target)
@@ -2516,7 +2510,7 @@ Http::get('/v1/messaging/topics/:topicId/subscribers/:subscriberId')
->dynamic($subscriber, Response::MODEL_SUBSCRIBER);
});
-Http::delete('/v1/messaging/topics/:topicId/subscribers/:subscriberId')
+App::delete('/v1/messaging/topics/:topicId/subscribers/:subscriberId')
->desc('Delete subscriber')
->groups(['api', 'messaging'])
->label('audits.event', 'subscriber.delete')
@@ -2535,9 +2529,8 @@ Http::delete('/v1/messaging/topics/:topicId/subscribers/:subscriberId')
->inject('queueForEvents')
->inject('dbForProject')
->inject('response')
- ->inject('authorization')
- ->action(function (string $topicId, string $subscriberId, Event $queueForEvents, Database $dbForProject, Response $response, Authorization $authorization) {
- $topic = $authorization->skip(fn () => $dbForProject->getDocument('topics', $topicId));
+ ->action(function (string $topicId, string $subscriberId, Event $queueForEvents, Database $dbForProject, Response $response) {
+ $topic = Authorization::skip(fn () => $dbForProject->getDocument('topics', $topicId));
if ($topic->isEmpty()) {
throw new Exception(Exception::TOPIC_NOT_FOUND);
@@ -2560,7 +2553,7 @@ Http::delete('/v1/messaging/topics/:topicId/subscribers/:subscriberId')
default => throw new Exception(Exception::TARGET_PROVIDER_INVALID_TYPE),
};
- $authorization->skip(fn () => $dbForProject->decreaseDocumentAttribute(
+ Authorization::skip(fn () => $dbForProject->decreaseDocumentAttribute(
'topics',
$topicId,
$totalAttribute,
@@ -2576,7 +2569,7 @@ Http::delete('/v1/messaging/topics/:topicId/subscribers/:subscriberId')
->noContent();
});
-Http::post('/v1/messaging/messages/email')
+App::post('/v1/messaging/messages/email')
->desc('Create email')
->groups(['api', 'messaging'])
->label('audits.event', 'message.create')
@@ -2728,7 +2721,7 @@ Http::post('/v1/messaging/messages/email')
->dynamic($message, Response::MODEL_MESSAGE);
});
-Http::post('/v1/messaging/messages/sms')
+App::post('/v1/messaging/messages/sms')
->desc('Create SMS')
->groups(['api', 'messaging'])
->label('audits.event', 'message.create')
@@ -2844,7 +2837,7 @@ Http::post('/v1/messaging/messages/sms')
->dynamic($message, Response::MODEL_MESSAGE);
});
-Http::post('/v1/messaging/messages/push')
+App::post('/v1/messaging/messages/push')
->desc('Create push notification')
->groups(['api', 'messaging'])
->label('audits.event', 'message.create')
@@ -3020,7 +3013,7 @@ Http::post('/v1/messaging/messages/push')
->dynamic($message, Response::MODEL_MESSAGE);
});
-Http::get('/v1/messaging/messages')
+App::get('/v1/messaging/messages')
->desc('List messages')
->groups(['api', 'messaging'])
->label('scope', 'messages.read')
@@ -3035,8 +3028,7 @@ Http::get('/v1/messaging/messages')
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
->inject('dbForProject')
->inject('response')
- ->inject('authorization')
- ->action(function (array $queries, string $search, Database $dbForProject, Response $response, Authorization $authorization) {
+ ->action(function (array $queries, string $search, Database $dbForProject, Response $response) {
try {
$queries = Query::parseQueries($queries);
} catch (QueryException $e) {
@@ -3057,7 +3049,7 @@ Http::get('/v1/messaging/messages')
if ($cursor) {
$messageId = $cursor->getValue();
- $cursorDocument = $authorization->skip(fn () => $dbForProject->getDocument('messages', $messageId));
+ $cursorDocument = Authorization::skip(fn () => $dbForProject->getDocument('messages', $messageId));
if ($cursorDocument->isEmpty()) {
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Message '{$messageId}' for the 'cursor' value not found.");
@@ -3072,7 +3064,7 @@ Http::get('/v1/messaging/messages')
]), Response::MODEL_MESSAGE_LIST);
});
-Http::get('/v1/messaging/messages/:messageId/logs')
+App::get('/v1/messaging/messages/:messageId/logs')
->desc('List message logs')
->groups(['api', 'messaging'])
->label('scope', 'messages.read')
@@ -3089,8 +3081,7 @@ Http::get('/v1/messaging/messages/:messageId/logs')
->inject('dbForProject')
->inject('locale')
->inject('geodb')
- ->inject('authorization')
- ->action(function (string $messageId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb, Authorization $authorization) {
+ ->action(function (string $messageId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) {
$message = $dbForProject->getDocument('messages', $messageId);
if ($message->isEmpty()) {
@@ -3162,7 +3153,7 @@ Http::get('/v1/messaging/messages/:messageId/logs')
]), Response::MODEL_LOG_LIST);
});
-Http::get('/v1/messaging/messages/:messageId/targets')
+App::get('/v1/messaging/messages/:messageId/targets')
->desc('List message targets')
->groups(['api', 'messaging'])
->label('scope', 'messages.read')
@@ -3227,7 +3218,7 @@ Http::get('/v1/messaging/messages/:messageId/targets')
]), Response::MODEL_TARGET_LIST);
});
-Http::get('/v1/messaging/messages/:messageId')
+App::get('/v1/messaging/messages/:messageId')
->desc('Get message')
->groups(['api', 'messaging'])
->label('scope', 'messages.read')
@@ -3251,7 +3242,7 @@ Http::get('/v1/messaging/messages/:messageId')
$response->dynamic($message, Response::MODEL_MESSAGE);
});
-Http::patch('/v1/messaging/messages/email/:messageId')
+App::patch('/v1/messaging/messages/email/:messageId')
->desc('Update email')
->groups(['api', 'messaging'])
->label('audits.event', 'message.update')
@@ -3451,7 +3442,7 @@ Http::patch('/v1/messaging/messages/email/:messageId')
->dynamic($message, Response::MODEL_MESSAGE);
});
-Http::patch('/v1/messaging/messages/sms/:messageId')
+App::patch('/v1/messaging/messages/sms/:messageId')
->desc('Update SMS')
->groups(['api', 'messaging'])
->label('audits.event', 'message.update')
@@ -3606,7 +3597,7 @@ Http::patch('/v1/messaging/messages/sms/:messageId')
->dynamic($message, Response::MODEL_MESSAGE);
});
-Http::patch('/v1/messaging/messages/push/:messageId')
+App::patch('/v1/messaging/messages/push/:messageId')
->desc('Update push notification')
->groups(['api', 'messaging'])
->label('audits.event', 'message.update')
@@ -3844,7 +3835,7 @@ Http::patch('/v1/messaging/messages/push/:messageId')
->dynamic($message, Response::MODEL_MESSAGE);
});
-Http::delete('/v1/messaging/messages/:messageId')
+App::delete('/v1/messaging/messages/:messageId')
->desc('Delete message')
->groups(['api', 'messaging'])
->label('audits.event', 'message.delete')
diff --git a/app/controllers/api/migrations.php b/app/controllers/api/migrations.php
index f39527c3e4..bb89d4a26f 100644
--- a/app/controllers/api/migrations.php
+++ b/app/controllers/api/migrations.php
@@ -9,6 +9,7 @@ use Appwrite\Role;
use Appwrite\Utopia\Database\Validator\Queries\Migrations;
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response;
+use Utopia\App;
use Utopia\Database\Database;
use Utopia\Database\DateTime;
use Utopia\Database\Document;
@@ -16,24 +17,23 @@ use Utopia\Database\Exception\Query as QueryException;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Query;
use Utopia\Database\Validator\UID;
-use Utopia\Http\Http;
-use Utopia\Http\Validator\ArrayList;
-use Utopia\Http\Validator\Host;
-use Utopia\Http\Validator\Integer;
-use Utopia\Http\Validator\Text;
-use Utopia\Http\Validator\URL;
-use Utopia\Http\Validator\WhiteList;
use Utopia\Migration\Sources\Appwrite;
use Utopia\Migration\Sources\Firebase;
use Utopia\Migration\Sources\NHost;
use Utopia\Migration\Sources\Supabase;
use Utopia\System\System;
+use Utopia\Validator\ArrayList;
+use Utopia\Validator\Host;
+use Utopia\Validator\Integer;
+use Utopia\Validator\Text;
+use Utopia\Validator\URL;
+use Utopia\Validator\WhiteList;
include_once __DIR__ . '/../shared/api.php';
-Http::post('/v1/migrations/appwrite')
+App::post('/v1/migrations/appwrite')
->groups(['api', 'migrations'])
- ->desc('Migrate Appwrite Data')
+ ->desc('Migrate Appwrite data')
->label('scope', 'migrations.write')
->label('event', 'migrations.[migrationId].create')
->label('audits.event', 'migration.create')
@@ -60,6 +60,7 @@ Http::post('/v1/migrations/appwrite')
'status' => 'pending',
'stage' => 'init',
'source' => Appwrite::getName(),
+ 'destination' => Appwrite::getName(),
'credentials' => [
'endpoint' => $endpoint,
'projectId' => $projectId,
@@ -85,9 +86,9 @@ Http::post('/v1/migrations/appwrite')
->dynamic($migration, Response::MODEL_MIGRATION);
});
-Http::post('/v1/migrations/firebase/oauth')
+App::post('/v1/migrations/firebase/oauth')
->groups(['api', 'migrations'])
- ->desc('Migrate Firebase Data (OAuth)')
+ ->desc('Migrate Firebase data (OAuth)')
->label('scope', 'migrations.write')
->label('event', 'migrations.[migrationId].create')
->label('audits.event', 'migration.create')
@@ -164,6 +165,7 @@ Http::post('/v1/migrations/firebase/oauth')
'status' => 'pending',
'stage' => 'init',
'source' => Firebase::getName(),
+ 'destination' => Appwrite::getName(),
'credentials' => [
'serviceAccount' => json_encode($serviceAccount),
],
@@ -187,9 +189,9 @@ Http::post('/v1/migrations/firebase/oauth')
->dynamic($migration, Response::MODEL_MIGRATION);
});
-Http::post('/v1/migrations/firebase')
+App::post('/v1/migrations/firebase')
->groups(['api', 'migrations'])
- ->desc('Migrate Firebase Data (Service Account)')
+ ->desc('Migrate Firebase data (Service Account)')
->label('scope', 'migrations.write')
->label('event', 'migrations.[migrationId].create')
->label('audits.event', 'migration.create')
@@ -224,6 +226,7 @@ Http::post('/v1/migrations/firebase')
'status' => 'pending',
'stage' => 'init',
'source' => Firebase::getName(),
+ 'destination' => Appwrite::getName(),
'credentials' => [
'serviceAccount' => $serviceAccount,
],
@@ -247,9 +250,9 @@ Http::post('/v1/migrations/firebase')
->dynamic($migration, Response::MODEL_MIGRATION);
});
-Http::post('/v1/migrations/supabase')
+App::post('/v1/migrations/supabase')
->groups(['api', 'migrations'])
- ->desc('Migrate Supabase Data')
+ ->desc('Migrate Supabase data')
->label('scope', 'migrations.write')
->label('event', 'migrations.[migrationId].create')
->label('audits.event', 'migration.create')
@@ -279,6 +282,7 @@ Http::post('/v1/migrations/supabase')
'status' => 'pending',
'stage' => 'init',
'source' => Supabase::getName(),
+ 'destination' => Appwrite::getName(),
'credentials' => [
'endpoint' => $endpoint,
'apiKey' => $apiKey,
@@ -307,9 +311,9 @@ Http::post('/v1/migrations/supabase')
->dynamic($migration, Response::MODEL_MIGRATION);
});
-Http::post('/v1/migrations/nhost')
+App::post('/v1/migrations/nhost')
->groups(['api', 'migrations'])
- ->desc('Migrate NHost Data')
+ ->desc('Migrate NHost data')
->label('scope', 'migrations.write')
->label('event', 'migrations.[migrationId].create')
->label('audits.event', 'migration.create')
@@ -340,6 +344,7 @@ Http::post('/v1/migrations/nhost')
'status' => 'pending',
'stage' => 'init',
'source' => NHost::getName(),
+ 'destination' => Appwrite::getName(),
'credentials' => [
'subdomain' => $subdomain,
'region' => $region,
@@ -369,9 +374,9 @@ Http::post('/v1/migrations/nhost')
->dynamic($migration, Response::MODEL_MIGRATION);
});
-Http::get('/v1/migrations')
+App::get('/v1/migrations')
->groups(['api', 'migrations'])
- ->desc('List Migrations')
+ ->desc('List migrations')
->label('scope', 'migrations.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'migrations')
@@ -422,9 +427,9 @@ Http::get('/v1/migrations')
]), Response::MODEL_MIGRATION_LIST);
});
-Http::get('/v1/migrations/:migrationId')
+App::get('/v1/migrations/:migrationId')
->groups(['api', 'migrations'])
- ->desc('Get Migration')
+ ->desc('Get migration')
->label('scope', 'migrations.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'migrations')
@@ -446,9 +451,9 @@ Http::get('/v1/migrations/:migrationId')
$response->dynamic($migration, Response::MODEL_MIGRATION);
});
-Http::get('/v1/migrations/appwrite/report')
+App::get('/v1/migrations/appwrite/report')
->groups(['api', 'migrations'])
- ->desc('Generate a report on Appwrite Data')
+ ->desc('Generate a report on Appwrite data')
->label('scope', 'migrations.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'migrations')
@@ -488,9 +493,9 @@ Http::get('/v1/migrations/appwrite/report')
->dynamic(new Document($report), Response::MODEL_MIGRATION_REPORT);
});
-Http::get('/v1/migrations/firebase/report')
+App::get('/v1/migrations/firebase/report')
->groups(['api', 'migrations'])
- ->desc('Generate a report on Firebase Data')
+ ->desc('Generate a report on Firebase data')
->label('scope', 'migrations.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'migrations')
@@ -535,9 +540,9 @@ Http::get('/v1/migrations/firebase/report')
->dynamic(new Document($report), Response::MODEL_MIGRATION_REPORT);
});
-Http::get('/v1/migrations/firebase/report/oauth')
+App::get('/v1/migrations/firebase/report/oauth')
->groups(['api', 'migrations'])
- ->desc('Generate a report on Firebase Data using OAuth')
+ ->desc('Generate a report on Firebase data using OAuth')
->label('scope', 'migrations.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'migrations')
@@ -626,8 +631,8 @@ Http::get('/v1/migrations/firebase/report/oauth')
->dynamic(new Document($report), Response::MODEL_MIGRATION_REPORT);
});
-Http::get('/v1/migrations/firebase/connect')
- ->desc('Authorize with firebase')
+App::get('/v1/migrations/firebase/connect')
+ ->desc('Authorize with Firebase')
->groups(['api', 'migrations'])
->label('scope', 'migrations.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
@@ -668,7 +673,7 @@ Http::get('/v1/migrations/firebase/connect')
->redirect($url);
});
-Http::get('/v1/migrations/firebase/redirect')
+App::get('/v1/migrations/firebase/redirect')
->desc('Capture and receive data on Firebase authorization')
->groups(['api', 'migrations'])
->label('scope', 'public')
@@ -780,8 +785,8 @@ Http::get('/v1/migrations/firebase/redirect')
->redirect($redirect);
});
-Http::get('/v1/migrations/firebase/projects')
- ->desc('List Firebase Projects')
+App::get('/v1/migrations/firebase/projects')
+ ->desc('List Firebase projects')
->groups(['api', 'migrations'])
->label('scope', 'migrations.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
@@ -869,8 +874,8 @@ Http::get('/v1/migrations/firebase/projects')
]), Response::MODEL_MIGRATION_FIREBASE_PROJECT_LIST);
});
-Http::get('/v1/migrations/firebase/deauthorize')
- ->desc('Revoke Appwrite\'s authorization to access Firebase Projects')
+App::get('/v1/migrations/firebase/deauthorize')
+ ->desc('Revoke Appwrite\'s authorization to access Firebase projects')
->groups(['api', 'migrations'])
->label('scope', 'migrations.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
@@ -897,7 +902,7 @@ Http::get('/v1/migrations/firebase/deauthorize')
$response->noContent();
});
-Http::get('/v1/migrations/supabase/report')
+App::get('/v1/migrations/supabase/report')
->groups(['api', 'migrations'])
->desc('Generate a report on Supabase Data')
->label('scope', 'migrations.write')
@@ -940,7 +945,7 @@ Http::get('/v1/migrations/supabase/report')
->dynamic(new Document($report), Response::MODEL_MIGRATION_REPORT);
});
-Http::get('/v1/migrations/nhost/report')
+App::get('/v1/migrations/nhost/report')
->groups(['api', 'migrations'])
->desc('Generate a report on NHost Data')
->label('scope', 'migrations.write')
@@ -983,9 +988,9 @@ Http::get('/v1/migrations/nhost/report')
->dynamic(new Document($report), Response::MODEL_MIGRATION_REPORT);
});
-Http::patch('/v1/migrations/:migrationId')
+App::patch('/v1/migrations/:migrationId')
->groups(['api', 'migrations'])
- ->desc('Retry Migration')
+ ->desc('Retry migration')
->label('scope', 'migrations.write')
->label('event', 'migrations.[migrationId].retry')
->label('audits.event', 'migration.retry')
@@ -1028,9 +1033,9 @@ Http::patch('/v1/migrations/:migrationId')
$response->noContent();
});
-Http::delete('/v1/migrations/:migrationId')
+App::delete('/v1/migrations/:migrationId')
->groups(['api', 'migrations'])
- ->desc('Delete Migration')
+ ->desc('Delete migration')
->label('scope', 'migrations.write')
->label('event', 'migrations.[migrationId].delete')
->label('audits.event', 'migrationId.delete')
diff --git a/app/controllers/api/project.php b/app/controllers/api/project.php
index 177e040bc4..6053326308 100644
--- a/app/controllers/api/project.php
+++ b/app/controllers/api/project.php
@@ -2,6 +2,7 @@
use Appwrite\Extend\Exception;
use Appwrite\Utopia\Response;
+use Utopia\App;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Exception\Duplicate as DuplicateException;
@@ -12,11 +13,10 @@ use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Datetime as DateTimeValidator;
use Utopia\Database\Validator\UID;
-use Utopia\Http\Http;
-use Utopia\Http\Validator\Text;
-use Utopia\Http\Validator\WhiteList;
+use Utopia\Validator\Text;
+use Utopia\Validator\WhiteList;
-Http::get('/v1/project/usage')
+App::get('/v1/project/usage')
->desc('Get project usage stats')
->groups(['api', 'usage'])
->label('scope', 'projects.read')
@@ -31,8 +31,7 @@ Http::get('/v1/project/usage')
->param('period', '1d', new WhiteList(['1h', '1d']), 'Period used', true)
->inject('response')
->inject('dbForProject')
- ->inject('authorization')
- ->action(function (string $startDate, string $endDate, string $period, Response $response, Database $dbForProject, Authorization $authorization) {
+ ->action(function (string $startDate, string $endDate, string $period, Response $response, Database $dbForProject) {
$stats = $total = $usage = [];
$format = 'Y-m-d 00:00:00';
$firstDay = (new DateTime($startDate))->format($format);
@@ -48,6 +47,7 @@ Http::get('/v1/project/usage')
METRIC_USERS,
METRIC_BUCKETS,
METRIC_FILES_STORAGE,
+ METRIC_DATABASES_STORAGE,
METRIC_DEPLOYMENTS_STORAGE,
METRIC_BUILDS_STORAGE
],
@@ -57,6 +57,7 @@ Http::get('/v1/project/usage')
METRIC_NETWORK_OUTBOUND,
METRIC_USERS,
METRIC_EXECUTIONS,
+ METRIC_DATABASES_STORAGE,
METRIC_EXECUTIONS_MB_SECONDS,
METRIC_BUILDS_MB_SECONDS
]
@@ -77,7 +78,7 @@ Http::get('/v1/project/usage')
'1d' => 'Y-m-d\T00:00:00.000P',
};
- $authorization->skip(function () use ($dbForProject, $firstDay, $lastDay, $period, $metrics, $limit, &$total, &$stats) {
+ Authorization::skip(function () use ($dbForProject, $firstDay, $lastDay, $period, $metrics, $limit, &$total, &$stats) {
foreach ($metrics['total'] as $metric) {
$result = $dbForProject->findOne('stats', [
Query::equal('metric', [$metric]),
@@ -183,6 +184,23 @@ Http::get('/v1/project/usage')
];
}, $dbForProject->find('buckets'));
+ $databasesStorageBreakdown = array_map(function ($database) use ($dbForProject) {
+ $id = $database->getId();
+ $name = $database->getAttribute('name');
+ $metric = str_replace('{databaseInternalId}', $database->getInternalId(), METRIC_DATABASE_ID_STORAGE);
+
+ $value = $dbForProject->findOne('stats', [
+ Query::equal('metric', [$metric]),
+ Query::equal('period', ['inf'])
+ ]);
+
+ return [
+ 'resourceId' => $id,
+ 'name' => $name,
+ 'value' => $value['value'] ?? 0,
+ ];
+ }, $dbForProject->find('databases'));
+
$functionsStorageBreakdown = array_map(function ($function) use ($dbForProject) {
$id = $function->getId();
$name = $function->getAttribute('name');
@@ -270,6 +288,7 @@ Http::get('/v1/project/usage')
'buildsMbSecondsTotal' => $total[METRIC_BUILDS_MB_SECONDS],
'documentsTotal' => $total[METRIC_DOCUMENTS],
'databasesTotal' => $total[METRIC_DATABASES],
+ 'databasesStorageTotal' => $total[METRIC_DATABASES_STORAGE],
'usersTotal' => $total[METRIC_USERS],
'bucketsTotal' => $total[METRIC_BUCKETS],
'filesStorageTotal' => $total[METRIC_FILES_STORAGE],
@@ -277,7 +296,10 @@ Http::get('/v1/project/usage')
'buildsStorageTotal' => $total[METRIC_BUILDS_STORAGE],
'deploymentsStorageTotal' => $total[METRIC_DEPLOYMENTS_STORAGE],
'executionsBreakdown' => $executionsBreakdown,
+ 'executionsMbSecondsBreakdown' => $executionsMbSecondsBreakdown,
+ 'buildsMbSecondsBreakdown' => $buildsMbSecondsBreakdown,
'bucketsBreakdown' => $bucketsBreakdown,
+ 'databasesStorageBreakdown' => $databasesStorageBreakdown,
'executionsMbSecondsBreakdown' => $executionsMbSecondsBreakdown,
'buildsMbSecondsBreakdown' => $buildsMbSecondsBreakdown,
'functionsStorageBreakdown' => $functionsStorageBreakdown,
@@ -286,8 +308,8 @@ Http::get('/v1/project/usage')
// Variables
-Http::post('/v1/project/variables')
- ->desc('Create Variable')
+App::post('/v1/project/variables')
+ ->desc('Create variable')
->groups(['api'])
->label('scope', 'projects.write')
->label('audits.event', 'variable.create')
@@ -341,8 +363,8 @@ Http::post('/v1/project/variables')
->dynamic($variable, Response::MODEL_VARIABLE);
});
-Http::get('/v1/project/variables')
- ->desc('List Variables')
+App::get('/v1/project/variables')
+ ->desc('List variables')
->groups(['api'])
->label('scope', 'projects.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
@@ -366,8 +388,8 @@ Http::get('/v1/project/variables')
]), Response::MODEL_VARIABLE_LIST);
});
-Http::get('/v1/project/variables/:variableId')
- ->desc('Get Variable')
+App::get('/v1/project/variables/:variableId')
+ ->desc('Get variable')
->groups(['api'])
->label('scope', 'projects.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
@@ -390,8 +412,8 @@ Http::get('/v1/project/variables/:variableId')
$response->dynamic($variable, Response::MODEL_VARIABLE);
});
-Http::put('/v1/project/variables/:variableId')
- ->desc('Update Variable')
+App::put('/v1/project/variables/:variableId')
+ ->desc('Update variable')
->groups(['api'])
->label('scope', 'projects.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
@@ -436,8 +458,8 @@ Http::put('/v1/project/variables/:variableId')
$response->dynamic($variable, Response::MODEL_VARIABLE);
});
-Http::delete('/v1/project/variables/:variableId')
- ->desc('Delete Variable')
+App::delete('/v1/project/variables/:variableId')
+ ->desc('Delete variable')
->groups(['api'])
->label('scope', 'projects.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php
index da5b4b882c..934793410b 100644
--- a/app/controllers/api/projects.php
+++ b/app/controllers/api/projects.php
@@ -13,16 +13,14 @@ use Appwrite\Network\Validator\Origin;
use Appwrite\Template\Template;
use Appwrite\Utopia\Database\Validator\ProjectId;
use Appwrite\Utopia\Database\Validator\Queries\Projects;
-use Appwrite\Utopia\Queue\Connections;
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response;
use PHPMailer\PHPMailer\PHPMailer;
use Utopia\Abuse\Adapters\Database\TimeLimit;
+use Utopia\App;
use Utopia\Audit\Audit;
use Utopia\Cache\Cache;
use Utopia\Config\Config;
-use Utopia\Database\Adapter\MariaDB;
-use Utopia\Database\Adapter\MySQL;
use Utopia\Database\Database;
use Utopia\Database\DateTime;
use Utopia\Database\Document;
@@ -32,25 +30,24 @@ use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Query;
-use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Datetime as DatetimeValidator;
use Utopia\Database\Validator\UID;
use Utopia\Domains\Validator\PublicDomain;
use Utopia\DSN\DSN;
-use Utopia\Http\Http;
-use Utopia\Http\Validator\ArrayList;
-use Utopia\Http\Validator\Boolean;
-use Utopia\Http\Validator\Hostname;
-use Utopia\Http\Validator\Integer;
-use Utopia\Http\Validator\Multiple;
-use Utopia\Http\Validator\Range;
-use Utopia\Http\Validator\Text;
-use Utopia\Http\Validator\URL;
-use Utopia\Http\Validator\WhiteList;
use Utopia\Locale\Locale;
+use Utopia\Pools\Group;
use Utopia\System\System;
+use Utopia\Validator\ArrayList;
+use Utopia\Validator\Boolean;
+use Utopia\Validator\Hostname;
+use Utopia\Validator\Integer;
+use Utopia\Validator\Multiple;
+use Utopia\Validator\Range;
+use Utopia\Validator\Text;
+use Utopia\Validator\URL;
+use Utopia\Validator\WhiteList;
-Http::init()
+App::init()
->groups(['projects'])
->inject('project')
->action(function (Document $project) {
@@ -59,7 +56,7 @@ Http::init()
}
});
-Http::post('/v1/projects')
+App::post('/v1/projects')
->desc('Create project')
->groups(['api', 'projects'])
->label('audits.event', 'projects.create')
@@ -89,9 +86,8 @@ Http::post('/v1/projects')
->inject('cache')
->inject('pools')
->inject('hooks')
- ->inject('authorization')
- ->inject('connections')
- ->action(function (string $projectId, string $name, string $teamId, string $region, string $description, string $logo, string $url, string $legalName, string $legalCountry, string $legalState, string $legalCity, string $legalAddress, string $legalTaxId, Request $request, Response $response, Database $dbForConsole, Cache $cache, array $pools, Hooks $hooks, Authorization $authorization, Connections $connections) {
+ ->action(function (string $projectId, string $name, string $teamId, string $region, string $description, string $logo, string $url, string $legalName, string $legalCountry, string $legalState, string $legalCity, string $legalAddress, string $legalTaxId, Request $request, Response $response, Database $dbForConsole, Cache $cache, Group $pools, Hooks $hooks) {
+
$team = $dbForConsole->getDocument('teams', $teamId);
if ($team->isEmpty()) {
@@ -193,21 +189,8 @@ Http::post('/v1/projects')
$dsn = new DSN('mysql://' . $dsn);
}
- $pool = $pools['pools-database-' . $dsn->getHost()]['pool'];
- $connectionDsn = $pools['pools-database-' . $dsn->getHost()]['dsn'];
- $connection = $pool->get();
- $connections->add($connection, $pool);
-
- $adapter = match ($connectionDsn->getScheme()) {
- 'mariadb' => new MariaDB($connection),
- 'mysql' => new MySQL($connection),
- default => null
- };
-
- $adapter->setDatabase($connectionDsn->getPath());
-
+ $adapter = $pools->get($dsn->getHost())->pop()->getResource();
$dbForProject = new Database($adapter, $cache);
- $dbForProject->setAuthorization($authorization);
if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) {
$dbForProject
@@ -225,8 +208,10 @@ Http::post('/v1/projects')
$audit = new Audit($dbForProject);
$audit->setup();
+
$abuse = new TimeLimit('', 0, 1, $dbForProject);
$abuse->setup();
+
/** @var array $collections */
$collections = Config::getParam('collections', [])['projects'] ?? [];
@@ -249,17 +234,17 @@ Http::post('/v1/projects')
// Collection already exists
}
}
- $connections->reclaim();
+
// Hook allowing instant project mirroring during migration
// Outside of migration, hook is not registered and has no effect
- $hooks->trigger('afterProjectCreation', [$project, $pools, $cache]);
+ $hooks->trigger('afterProjectCreation', [ $project, $pools, $cache ]);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($project, Response::MODEL_PROJECT);
});
-Http::get('/v1/projects')
+App::get('/v1/projects')
->desc('List projects')
->groups(['api', 'projects'])
->label('scope', 'projects.read')
@@ -274,6 +259,7 @@ Http::get('/v1/projects')
->inject('response')
->inject('dbForConsole')
->action(function (array $queries, string $search, Response $response, Database $dbForConsole) {
+
try {
$queries = Query::parseQueries($queries);
} catch (QueryException $e) {
@@ -311,7 +297,7 @@ Http::get('/v1/projects')
]), Response::MODEL_PROJECT_LIST);
});
-Http::get('/v1/projects/:projectId')
+App::get('/v1/projects/:projectId')
->desc('Get project')
->groups(['api', 'projects'])
->label('scope', 'projects.read')
@@ -325,6 +311,7 @@ Http::get('/v1/projects/:projectId')
->inject('response')
->inject('dbForConsole')
->action(function (string $projectId, Response $response, Database $dbForConsole) {
+
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
@@ -334,7 +321,7 @@ Http::get('/v1/projects/:projectId')
$response->dynamic($project, Response::MODEL_PROJECT);
});
-Http::patch('/v1/projects/:projectId')
+App::patch('/v1/projects/:projectId')
->desc('Update project')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
@@ -358,34 +345,31 @@ Http::patch('/v1/projects/:projectId')
->inject('response')
->inject('dbForConsole')
->action(function (string $projectId, string $name, string $description, string $logo, string $url, string $legalName, string $legalCountry, string $legalState, string $legalCity, string $legalAddress, string $legalTaxId, Response $response, Database $dbForConsole) {
+
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
throw new Exception(Exception::PROJECT_NOT_FOUND);
}
- $project = $dbForConsole->updateDocument(
- 'projects',
- $project->getId(),
- $project
- ->setAttribute('name', $name)
- ->setAttribute('description', $description)
- ->setAttribute('logo', $logo)
- ->setAttribute('url', $url)
- ->setAttribute('legalName', $legalName)
- ->setAttribute('legalCountry', $legalCountry)
- ->setAttribute('legalState', $legalState)
- ->setAttribute('legalCity', $legalCity)
- ->setAttribute('legalAddress', $legalAddress)
- ->setAttribute('legalTaxId', $legalTaxId)
- ->setAttribute('search', implode(' ', [$projectId, $name]))
- );
+ $project = $dbForConsole->updateDocument('projects', $project->getId(), $project
+ ->setAttribute('name', $name)
+ ->setAttribute('description', $description)
+ ->setAttribute('logo', $logo)
+ ->setAttribute('url', $url)
+ ->setAttribute('legalName', $legalName)
+ ->setAttribute('legalCountry', $legalCountry)
+ ->setAttribute('legalState', $legalState)
+ ->setAttribute('legalCity', $legalCity)
+ ->setAttribute('legalAddress', $legalAddress)
+ ->setAttribute('legalTaxId', $legalTaxId)
+ ->setAttribute('search', implode(' ', [$projectId, $name])));
$response->dynamic($project, Response::MODEL_PROJECT);
});
-Http::patch('/v1/projects/:projectId/team')
- ->desc('Update Project Team')
+App::patch('/v1/projects/:projectId/team')
+ ->desc('Update project team')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
@@ -399,6 +383,7 @@ Http::patch('/v1/projects/:projectId/team')
->inject('response')
->inject('dbForConsole')
->action(function (string $projectId, string $teamId, Response $response, Database $dbForConsole) {
+
$project = $dbForConsole->getDocument('projects', $projectId);
$team = $dbForConsole->getDocument('teams', $teamId);
@@ -451,7 +436,7 @@ Http::patch('/v1/projects/:projectId/team')
$response->dynamic($project, Response::MODEL_PROJECT);
});
-Http::patch('/v1/projects/:projectId/service')
+App::patch('/v1/projects/:projectId/service')
->desc('Update service status')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
@@ -467,6 +452,7 @@ Http::patch('/v1/projects/:projectId/service')
->inject('response')
->inject('dbForConsole')
->action(function (string $projectId, string $service, bool $status, Response $response, Database $dbForConsole) {
+
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
@@ -481,7 +467,7 @@ Http::patch('/v1/projects/:projectId/service')
$response->dynamic($project, Response::MODEL_PROJECT);
});
-Http::patch('/v1/projects/:projectId/service/all')
+App::patch('/v1/projects/:projectId/service/all')
->desc('Update all service status')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
@@ -496,6 +482,7 @@ Http::patch('/v1/projects/:projectId/service/all')
->inject('response')
->inject('dbForConsole')
->action(function (string $projectId, bool $status, Response $response, Database $dbForConsole) {
+
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
@@ -514,7 +501,7 @@ Http::patch('/v1/projects/:projectId/service/all')
$response->dynamic($project, Response::MODEL_PROJECT);
});
-Http::patch('/v1/projects/:projectId/api')
+App::patch('/v1/projects/:projectId/api')
->desc('Update API status')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
@@ -530,6 +517,7 @@ Http::patch('/v1/projects/:projectId/api')
->inject('response')
->inject('dbForConsole')
->action(function (string $projectId, string $api, bool $status, Response $response, Database $dbForConsole) {
+
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
@@ -544,7 +532,7 @@ Http::patch('/v1/projects/:projectId/api')
$response->dynamic($project, Response::MODEL_PROJECT);
});
-Http::patch('/v1/projects/:projectId/api/all')
+App::patch('/v1/projects/:projectId/api/all')
->desc('Update all API status')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
@@ -559,6 +547,7 @@ Http::patch('/v1/projects/:projectId/api/all')
->inject('response')
->inject('dbForConsole')
->action(function (string $projectId, bool $status, Response $response, Database $dbForConsole) {
+
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
@@ -577,7 +566,7 @@ Http::patch('/v1/projects/:projectId/api/all')
$response->dynamic($project, Response::MODEL_PROJECT);
});
-Http::patch('/v1/projects/:projectId/oauth2')
+App::patch('/v1/projects/:projectId/oauth2')
->desc('Update project OAuth2')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
@@ -595,6 +584,7 @@ Http::patch('/v1/projects/:projectId/oauth2')
->inject('response')
->inject('dbForConsole')
->action(function (string $projectId, string $provider, ?string $appId, ?string $secret, ?bool $enabled, Response $response, Database $dbForConsole) {
+
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
@@ -620,7 +610,7 @@ Http::patch('/v1/projects/:projectId/oauth2')
$response->dynamic($project, Response::MODEL_PROJECT);
});
-Http::patch('/v1/projects/:projectId/auth/session-alerts')
+App::patch('/v1/projects/:projectId/auth/session-alerts')
->desc('Update project sessions emails')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
@@ -651,7 +641,7 @@ Http::patch('/v1/projects/:projectId/auth/session-alerts')
$response->dynamic($project, Response::MODEL_PROJECT);
});
-Http::patch('/v1/projects/:projectId/auth/limit')
+App::patch('/v1/projects/:projectId/auth/limit')
->desc('Update project users limit')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
@@ -666,6 +656,7 @@ Http::patch('/v1/projects/:projectId/auth/limit')
->inject('response')
->inject('dbForConsole')
->action(function (string $projectId, int $limit, Response $response, Database $dbForConsole) {
+
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
@@ -675,17 +666,13 @@ Http::patch('/v1/projects/:projectId/auth/limit')
$auths = $project->getAttribute('auths', []);
$auths['limit'] = $limit;
- $dbForConsole->updateDocument(
- 'projects',
- $project->getId(),
- $project
- ->setAttribute('auths', $auths)
- );
+ $dbForConsole->updateDocument('projects', $project->getId(), $project
+ ->setAttribute('auths', $auths));
$response->dynamic($project, Response::MODEL_PROJECT);
});
-Http::patch('/v1/projects/:projectId/auth/duration')
+App::patch('/v1/projects/:projectId/auth/duration')
->desc('Update project authentication duration')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
@@ -700,6 +687,7 @@ Http::patch('/v1/projects/:projectId/auth/duration')
->inject('response')
->inject('dbForConsole')
->action(function (string $projectId, int $duration, Response $response, Database $dbForConsole) {
+
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
@@ -709,17 +697,13 @@ Http::patch('/v1/projects/:projectId/auth/duration')
$auths = $project->getAttribute('auths', []);
$auths['duration'] = $duration;
- $dbForConsole->updateDocument(
- 'projects',
- $project->getId(),
- $project
- ->setAttribute('auths', $auths)
- );
+ $dbForConsole->updateDocument('projects', $project->getId(), $project
+ ->setAttribute('auths', $auths));
$response->dynamic($project, Response::MODEL_PROJECT);
});
-Http::patch('/v1/projects/:projectId/auth/:method')
+App::patch('/v1/projects/:projectId/auth/:method')
->desc('Update project auth method status. Use this endpoint to enable or disable a given auth method for this project.')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
@@ -735,9 +719,10 @@ Http::patch('/v1/projects/:projectId/auth/:method')
->inject('response')
->inject('dbForConsole')
->action(function (string $projectId, string $method, bool $status, Response $response, Database $dbForConsole) {
+
$project = $dbForConsole->getDocument('projects', $projectId);
- $authConfig = Config::getParam('auth')[$method] ?? [];
- $authKey = $authConfig['key'] ?? '';
+ $auth = Config::getParam('auth')[$method] ?? [];
+ $authKey = $auth['key'] ?? '';
$status = ($status === '1' || $status === 'true' || $status === 1 || $status === true);
if ($project->isEmpty()) {
@@ -752,7 +737,7 @@ Http::patch('/v1/projects/:projectId/auth/:method')
$response->dynamic($project, Response::MODEL_PROJECT);
});
-Http::patch('/v1/projects/:projectId/auth/password-history')
+App::patch('/v1/projects/:projectId/auth/password-history')
->desc('Update authentication password history. Use this endpoint to set the number of password history to save and 0 to disable password history.')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
@@ -767,6 +752,7 @@ Http::patch('/v1/projects/:projectId/auth/password-history')
->inject('response')
->inject('dbForConsole')
->action(function (string $projectId, int $limit, Response $response, Database $dbForConsole) {
+
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
@@ -776,17 +762,13 @@ Http::patch('/v1/projects/:projectId/auth/password-history')
$auths = $project->getAttribute('auths', []);
$auths['passwordHistory'] = $limit;
- $dbForConsole->updateDocument(
- 'projects',
- $project->getId(),
- $project
- ->setAttribute('auths', $auths)
- );
+ $dbForConsole->updateDocument('projects', $project->getId(), $project
+ ->setAttribute('auths', $auths));
$response->dynamic($project, Response::MODEL_PROJECT);
});
-Http::patch('/v1/projects/:projectId/auth/password-dictionary')
+App::patch('/v1/projects/:projectId/auth/password-dictionary')
->desc('Update authentication password dictionary status. Use this endpoint to enable or disable the dicitonary check for user password')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
@@ -801,6 +783,7 @@ Http::patch('/v1/projects/:projectId/auth/password-dictionary')
->inject('response')
->inject('dbForConsole')
->action(function (string $projectId, bool $enabled, Response $response, Database $dbForConsole) {
+
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
@@ -810,17 +793,13 @@ Http::patch('/v1/projects/:projectId/auth/password-dictionary')
$auths = $project->getAttribute('auths', []);
$auths['passwordDictionary'] = $enabled;
- $dbForConsole->updateDocument(
- 'projects',
- $project->getId(),
- $project
- ->setAttribute('auths', $auths)
- );
+ $dbForConsole->updateDocument('projects', $project->getId(), $project
+ ->setAttribute('auths', $auths));
$response->dynamic($project, Response::MODEL_PROJECT);
});
-Http::patch('/v1/projects/:projectId/auth/personal-data')
+App::patch('/v1/projects/:projectId/auth/personal-data')
->desc('Enable or disable checking user passwords for similarity with their personal data.')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
@@ -835,6 +814,7 @@ Http::patch('/v1/projects/:projectId/auth/personal-data')
->inject('response')
->inject('dbForConsole')
->action(function (string $projectId, bool $enabled, Response $response, Database $dbForConsole) {
+
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
@@ -844,17 +824,13 @@ Http::patch('/v1/projects/:projectId/auth/personal-data')
$auths = $project->getAttribute('auths', []);
$auths['personalDataCheck'] = $enabled;
- $dbForConsole->updateDocument(
- 'projects',
- $project->getId(),
- $project
- ->setAttribute('auths', $auths)
- );
+ $dbForConsole->updateDocument('projects', $project->getId(), $project
+ ->setAttribute('auths', $auths));
$response->dynamic($project, Response::MODEL_PROJECT);
});
-Http::patch('/v1/projects/:projectId/auth/max-sessions')
+App::patch('/v1/projects/:projectId/auth/max-sessions')
->desc('Update project user sessions limit')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
@@ -869,6 +845,7 @@ Http::patch('/v1/projects/:projectId/auth/max-sessions')
->inject('response')
->inject('dbForConsole')
->action(function (string $projectId, int $limit, Response $response, Database $dbForConsole) {
+
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
@@ -878,17 +855,13 @@ Http::patch('/v1/projects/:projectId/auth/max-sessions')
$auths = $project->getAttribute('auths', []);
$auths['maxSessions'] = $limit;
- $dbForConsole->updateDocument(
- 'projects',
- $project->getId(),
- $project
- ->setAttribute('auths', $auths)
- );
+ $dbForConsole->updateDocument('projects', $project->getId(), $project
+ ->setAttribute('auths', $auths));
$response->dynamic($project, Response::MODEL_PROJECT);
});
-Http::patch('/v1/projects/:projectId/auth/mock-numbers')
+App::patch('/v1/projects/:projectId/auth/mock-numbers')
->desc('Update the mock numbers for the project')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
@@ -927,7 +900,7 @@ Http::patch('/v1/projects/:projectId/auth/mock-numbers')
$response->dynamic($project, Response::MODEL_PROJECT);
});
-Http::delete('/v1/projects/:projectId')
+App::delete('/v1/projects/:projectId')
->desc('Delete project')
->groups(['api', 'projects'])
->label('audits.event', 'projects.delete')
@@ -950,6 +923,7 @@ Http::delete('/v1/projects/:projectId')
}
$queueForDeletes
+ ->setProject($project)
->setType(DELETE_TYPE_DOCUMENT)
->setDocument($project);
@@ -962,7 +936,7 @@ Http::delete('/v1/projects/:projectId')
// Webhooks
-Http::post('/v1/projects/:projectId/webhooks')
+App::post('/v1/projects/:projectId/webhooks')
->desc('Create webhook')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
@@ -983,13 +957,14 @@ Http::post('/v1/projects/:projectId/webhooks')
->inject('response')
->inject('dbForConsole')
->action(function (string $projectId, string $name, bool $enabled, array $events, string $url, bool $security, string $httpUser, string $httpPass, Response $response, Database $dbForConsole) {
+
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
throw new Exception(Exception::PROJECT_NOT_FOUND);
}
- $security = (bool)filter_var($security, FILTER_VALIDATE_BOOLEAN);
+ $security = (bool) filter_var($security, FILTER_VALIDATE_BOOLEAN);
$webhook = new Document([
'$id' => ID::unique(),
@@ -1019,7 +994,7 @@ Http::post('/v1/projects/:projectId/webhooks')
->dynamic($webhook, Response::MODEL_WEBHOOK);
});
-Http::get('/v1/projects/:projectId/webhooks')
+App::get('/v1/projects/:projectId/webhooks')
->desc('List webhooks')
->groups(['api', 'projects'])
->label('scope', 'projects.read')
@@ -1033,6 +1008,7 @@ Http::get('/v1/projects/:projectId/webhooks')
->inject('response')
->inject('dbForConsole')
->action(function (string $projectId, Response $response, Database $dbForConsole) {
+
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
@@ -1050,7 +1026,7 @@ Http::get('/v1/projects/:projectId/webhooks')
]), Response::MODEL_WEBHOOK_LIST);
});
-Http::get('/v1/projects/:projectId/webhooks/:webhookId')
+App::get('/v1/projects/:projectId/webhooks/:webhookId')
->desc('Get webhook')
->groups(['api', 'projects'])
->label('scope', 'projects.read')
@@ -1065,6 +1041,7 @@ Http::get('/v1/projects/:projectId/webhooks/:webhookId')
->inject('response')
->inject('dbForConsole')
->action(function (string $projectId, string $webhookId, Response $response, Database $dbForConsole) {
+
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
@@ -1083,7 +1060,7 @@ Http::get('/v1/projects/:projectId/webhooks/:webhookId')
$response->dynamic($webhook, Response::MODEL_WEBHOOK);
});
-Http::put('/v1/projects/:projectId/webhooks/:webhookId')
+App::put('/v1/projects/:projectId/webhooks/:webhookId')
->desc('Update webhook')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
@@ -1105,6 +1082,7 @@ Http::put('/v1/projects/:projectId/webhooks/:webhookId')
->inject('response')
->inject('dbForConsole')
->action(function (string $projectId, string $webhookId, string $name, bool $enabled, array $events, string $url, bool $security, string $httpUser, string $httpPass, Response $response, Database $dbForConsole) {
+
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
@@ -1141,7 +1119,7 @@ Http::put('/v1/projects/:projectId/webhooks/:webhookId')
$response->dynamic($webhook, Response::MODEL_WEBHOOK);
});
-Http::patch('/v1/projects/:projectId/webhooks/:webhookId/signature')
+App::patch('/v1/projects/:projectId/webhooks/:webhookId/signature')
->desc('Update webhook signature key')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
@@ -1156,6 +1134,7 @@ Http::patch('/v1/projects/:projectId/webhooks/:webhookId/signature')
->inject('response')
->inject('dbForConsole')
->action(function (string $projectId, string $webhookId, Response $response, Database $dbForConsole) {
+
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
@@ -1179,7 +1158,7 @@ Http::patch('/v1/projects/:projectId/webhooks/:webhookId/signature')
$response->dynamic($webhook, Response::MODEL_WEBHOOK);
});
-Http::delete('/v1/projects/:projectId/webhooks/:webhookId')
+App::delete('/v1/projects/:projectId/webhooks/:webhookId')
->desc('Delete webhook')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
@@ -1193,6 +1172,7 @@ Http::delete('/v1/projects/:projectId/webhooks/:webhookId')
->inject('response')
->inject('dbForConsole')
->action(function (string $projectId, string $webhookId, Response $response, Database $dbForConsole) {
+
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
@@ -1217,10 +1197,10 @@ Http::delete('/v1/projects/:projectId/webhooks/:webhookId')
// Keys
-Http::post('/v1/projects/:projectId/keys')
+App::post('/v1/projects/:projectId/keys')
->desc('Create key')
->groups(['api', 'projects'])
- ->label('scope', 'projects.write')
+ ->label('scope', 'keys.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')
->label('sdk.method', 'createKey')
@@ -1234,6 +1214,7 @@ Http::post('/v1/projects/:projectId/keys')
->inject('response')
->inject('dbForConsole')
->action(function (string $projectId, string $name, array $scopes, ?string $expire, Response $response, Database $dbForConsole) {
+
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
@@ -1266,10 +1247,10 @@ Http::post('/v1/projects/:projectId/keys')
->dynamic($key, Response::MODEL_KEY);
});
-Http::get('/v1/projects/:projectId/keys')
+App::get('/v1/projects/:projectId/keys')
->desc('List keys')
->groups(['api', 'projects'])
- ->label('scope', 'projects.read')
+ ->label('scope', 'keys.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')
->label('sdk.method', 'listKeys')
@@ -1280,6 +1261,7 @@ Http::get('/v1/projects/:projectId/keys')
->inject('response')
->inject('dbForConsole')
->action(function (string $projectId, Response $response, Database $dbForConsole) {
+
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
@@ -1297,10 +1279,10 @@ Http::get('/v1/projects/:projectId/keys')
]), Response::MODEL_KEY_LIST);
});
-Http::get('/v1/projects/:projectId/keys/:keyId')
+App::get('/v1/projects/:projectId/keys/:keyId')
->desc('Get key')
->groups(['api', 'projects'])
- ->label('scope', 'projects.read')
+ ->label('scope', 'keys.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')
->label('sdk.method', 'getKey')
@@ -1312,6 +1294,7 @@ Http::get('/v1/projects/:projectId/keys/:keyId')
->inject('response')
->inject('dbForConsole')
->action(function (string $projectId, string $keyId, Response $response, Database $dbForConsole) {
+
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
@@ -1330,10 +1313,10 @@ Http::get('/v1/projects/:projectId/keys/:keyId')
$response->dynamic($key, Response::MODEL_KEY);
});
-Http::put('/v1/projects/:projectId/keys/:keyId')
+App::put('/v1/projects/:projectId/keys/:keyId')
->desc('Update key')
->groups(['api', 'projects'])
- ->label('scope', 'projects.write')
+ ->label('scope', 'keys.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')
->label('sdk.method', 'updateKey')
@@ -1348,6 +1331,7 @@ Http::put('/v1/projects/:projectId/keys/:keyId')
->inject('response')
->inject('dbForConsole')
->action(function (string $projectId, string $keyId, string $name, array $scopes, ?string $expire, Response $response, Database $dbForConsole) {
+
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
@@ -1375,10 +1359,10 @@ Http::put('/v1/projects/:projectId/keys/:keyId')
$response->dynamic($key, Response::MODEL_KEY);
});
-Http::delete('/v1/projects/:projectId/keys/:keyId')
+App::delete('/v1/projects/:projectId/keys/:keyId')
->desc('Delete key')
->groups(['api', 'projects'])
- ->label('scope', 'projects.write')
+ ->label('scope', 'keys.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')
->label('sdk.method', 'deleteKey')
@@ -1389,6 +1373,7 @@ Http::delete('/v1/projects/:projectId/keys/:keyId')
->inject('response')
->inject('dbForConsole')
->action(function (string $projectId, string $keyId, Response $response, Database $dbForConsole) {
+
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
@@ -1413,7 +1398,7 @@ Http::delete('/v1/projects/:projectId/keys/:keyId')
// JWT Keys
-Http::post('/v1/projects/:projectId/jwts')
+App::post('/v1/projects/:projectId/jwts')
->groups(['api', 'projects'])
->desc('Create JWT')
->label('scope', 'projects.write')
@@ -1448,11 +1433,11 @@ Http::post('/v1/projects/:projectId/jwts')
// Platforms
-Http::post('/v1/projects/:projectId/platforms')
+App::post('/v1/projects/:projectId/platforms')
->desc('Create platform')
->groups(['api', 'projects'])
->label('audits.event', 'platforms.create')
- ->label('scope', 'projects.write')
+ ->label('scope', 'platforms.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')
->label('sdk.method', 'createPlatform')
@@ -1499,10 +1484,10 @@ Http::post('/v1/projects/:projectId/platforms')
->dynamic($platform, Response::MODEL_PLATFORM);
});
-Http::get('/v1/projects/:projectId/platforms')
+App::get('/v1/projects/:projectId/platforms')
->desc('List platforms')
->groups(['api', 'projects'])
- ->label('scope', 'projects.read')
+ ->label('scope', 'platforms.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')
->label('sdk.method', 'listPlatforms')
@@ -1513,6 +1498,7 @@ Http::get('/v1/projects/:projectId/platforms')
->inject('response')
->inject('dbForConsole')
->action(function (string $projectId, Response $response, Database $dbForConsole) {
+
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
@@ -1530,10 +1516,10 @@ Http::get('/v1/projects/:projectId/platforms')
]), Response::MODEL_PLATFORM_LIST);
});
-Http::get('/v1/projects/:projectId/platforms/:platformId')
+App::get('/v1/projects/:projectId/platforms/:platformId')
->desc('Get platform')
->groups(['api', 'projects'])
- ->label('scope', 'projects.read')
+ ->label('scope', 'platforms.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')
->label('sdk.method', 'getPlatform')
@@ -1545,6 +1531,7 @@ Http::get('/v1/projects/:projectId/platforms/:platformId')
->inject('response')
->inject('dbForConsole')
->action(function (string $projectId, string $platformId, Response $response, Database $dbForConsole) {
+
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
@@ -1563,10 +1550,10 @@ Http::get('/v1/projects/:projectId/platforms/:platformId')
$response->dynamic($platform, Response::MODEL_PLATFORM);
});
-Http::put('/v1/projects/:projectId/platforms/:platformId')
+App::put('/v1/projects/:projectId/platforms/:platformId')
->desc('Update platform')
->groups(['api', 'projects'])
- ->label('scope', 'projects.write')
+ ->label('scope', 'platforms.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')
->label('sdk.method', 'updatePlatform')
@@ -1610,11 +1597,11 @@ Http::put('/v1/projects/:projectId/platforms/:platformId')
$response->dynamic($platform, Response::MODEL_PLATFORM);
});
-Http::delete('/v1/projects/:projectId/platforms/:platformId')
+App::delete('/v1/projects/:projectId/platforms/:platformId')
->desc('Delete platform')
->groups(['api', 'projects'])
->label('audits.event', 'platforms.delete')
- ->label('scope', 'projects.write')
+ ->label('scope', 'platforms.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')
->label('sdk.method', 'deletePlatform')
@@ -1625,6 +1612,7 @@ Http::delete('/v1/projects/:projectId/platforms/:platformId')
->inject('response')
->inject('dbForConsole')
->action(function (string $projectId, string $platformId, Response $response, Database $dbForConsole) {
+
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
@@ -1649,7 +1637,7 @@ Http::delete('/v1/projects/:projectId/platforms/:platformId')
// CUSTOM SMTP and Templates
-Http::patch('/v1/projects/:projectId/smtp')
+App::patch('/v1/projects/:projectId/smtp')
->desc('Update SMTP')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
@@ -1672,6 +1660,7 @@ Http::patch('/v1/projects/:projectId/smtp')
->inject('response')
->inject('dbForConsole')
->action(function (string $projectId, bool $enabled, string $senderName, string $senderEmail, string $replyTo, string $host, int $port, string $username, string $password, string $secure, Response $response, Database $dbForConsole) {
+
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
@@ -1738,7 +1727,7 @@ Http::patch('/v1/projects/:projectId/smtp')
$response->dynamic($project, Response::MODEL_PROJECT);
});
-Http::post('/v1/projects/:projectId/smtp/tests')
+App::post('/v1/projects/:projectId/smtp/tests')
->desc('Create SMTP test')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
@@ -1756,7 +1745,7 @@ Http::post('/v1/projects/:projectId/smtp/tests')
->param('port', 587, new Integer(), 'SMTP server port', true)
->param('username', '', new Text(0, 0), 'SMTP server username', true)
->param('password', '', new Text(0, 0), 'SMTP server password', true)
- ->param('secure', '', new WhiteList(['tls'], true), 'Does SMTP server use secure connection', true)
+ ->param('secure', '', new WhiteList(['tls', 'ssl'], true), 'Does SMTP server use secure connection', true)
->inject('response')
->inject('dbForConsole')
->inject('queueForMails')
@@ -1797,7 +1786,7 @@ Http::post('/v1/projects/:projectId/smtp/tests')
return $response->noContent();
});
-Http::get('/v1/projects/:projectId/templates/sms/:type/:locale')
+App::get('/v1/projects/:projectId/templates/sms/:type/:locale')
->desc('Get custom SMS template')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
@@ -1813,6 +1802,7 @@ Http::get('/v1/projects/:projectId/templates/sms/:type/:locale')
->inject('response')
->inject('dbForConsole')
->action(function (string $projectId, string $type, string $locale, Response $response, Database $dbForConsole) {
+
throw new Exception(Exception::GENERAL_NOT_IMPLEMENTED);
$project = $dbForConsole->getDocument('projects', $projectId);
@@ -1822,7 +1812,7 @@ Http::get('/v1/projects/:projectId/templates/sms/:type/:locale')
}
$templates = $project->getAttribute('templates', []);
- $template = $templates['sms.' . $type . '-' . $locale] ?? null;
+ $template = $templates['sms.' . $type . '-' . $locale] ?? null;
if (is_null($template)) {
$template = [
@@ -1837,7 +1827,7 @@ Http::get('/v1/projects/:projectId/templates/sms/:type/:locale')
});
-Http::get('/v1/projects/:projectId/templates/email/:type/:locale')
+App::get('/v1/projects/:projectId/templates/email/:type/:locale')
->desc('Get custom email template')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
@@ -1853,6 +1843,7 @@ Http::get('/v1/projects/:projectId/templates/email/:type/:locale')
->inject('response')
->inject('dbForConsole')
->action(function (string $projectId, string $type, string $locale, Response $response, Database $dbForConsole) {
+
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
@@ -1860,7 +1851,7 @@ Http::get('/v1/projects/:projectId/templates/email/:type/:locale')
}
$templates = $project->getAttribute('templates', []);
- $template = $templates['email.' . $type . '-' . $locale] ?? null;
+ $template = $templates['email.' . $type . '-' . $locale] ?? null;
$localeObj = new Locale($locale);
if (is_null($template)) {
@@ -1868,7 +1859,7 @@ Http::get('/v1/projects/:projectId/templates/email/:type/:locale')
$message
->setParam('{{hello}}', $localeObj->getText("emails.{$type}.hello"))
->setParam('{{footer}}', $localeObj->getText("emails.{$type}.footer"))
- ->setParam('{{body}}', $localeObj->getText('emails.' . $type . '.body'), escape: false)
+ ->setParam('{{body}}', $localeObj->getText('emails.' . $type . '.body'), escapeHtml: false)
->setParam('{{thanks}}', $localeObj->getText("emails.{$type}.thanks"))
->setParam('{{signature}}', $localeObj->getText("emails.{$type}.signature"))
->setParam('{{direction}}', $localeObj->getText('settings.direction'));
@@ -1888,7 +1879,7 @@ Http::get('/v1/projects/:projectId/templates/email/:type/:locale')
$response->dynamic(new Document($template), Response::MODEL_EMAIL_TEMPLATE);
});
-Http::patch('/v1/projects/:projectId/templates/sms/:type/:locale')
+App::patch('/v1/projects/:projectId/templates/sms/:type/:locale')
->desc('Update custom SMS template')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
@@ -1905,6 +1896,7 @@ Http::patch('/v1/projects/:projectId/templates/sms/:type/:locale')
->inject('response')
->inject('dbForConsole')
->action(function (string $projectId, string $type, string $locale, string $message, Response $response, Database $dbForConsole) {
+
throw new Exception(Exception::GENERAL_NOT_IMPLEMENTED);
$project = $dbForConsole->getDocument('projects', $projectId);
@@ -1927,7 +1919,7 @@ Http::patch('/v1/projects/:projectId/templates/sms/:type/:locale')
]), Response::MODEL_SMS_TEMPLATE);
});
-Http::patch('/v1/projects/:projectId/templates/email/:type/:locale')
+App::patch('/v1/projects/:projectId/templates/email/:type/:locale')
->desc('Update custom email templates')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
@@ -1948,6 +1940,7 @@ Http::patch('/v1/projects/:projectId/templates/email/:type/:locale')
->inject('response')
->inject('dbForConsole')
->action(function (string $projectId, string $type, string $locale, string $subject, string $message, string $senderName, string $senderEmail, string $replyTo, Response $response, Database $dbForConsole) {
+
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
@@ -1976,7 +1969,7 @@ Http::patch('/v1/projects/:projectId/templates/email/:type/:locale')
]), Response::MODEL_EMAIL_TEMPLATE);
});
-Http::delete('/v1/projects/:projectId/templates/sms/:type/:locale')
+App::delete('/v1/projects/:projectId/templates/sms/:type/:locale')
->desc('Reset custom SMS template')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
@@ -1992,6 +1985,7 @@ Http::delete('/v1/projects/:projectId/templates/sms/:type/:locale')
->inject('response')
->inject('dbForConsole')
->action(function (string $projectId, string $type, string $locale, Response $response, Database $dbForConsole) {
+
throw new Exception(Exception::GENERAL_NOT_IMPLEMENTED);
$project = $dbForConsole->getDocument('projects', $projectId);
@@ -2001,7 +1995,7 @@ Http::delete('/v1/projects/:projectId/templates/sms/:type/:locale')
}
$templates = $project->getAttribute('templates', []);
- $template = $templates['sms.' . $type . '-' . $locale] ?? null;
+ $template = $templates['sms.' . $type . '-' . $locale] ?? null;
if (is_null($template)) {
throw new Exception(Exception::PROJECT_TEMPLATE_DEFAULT_DELETION);
@@ -2018,7 +2012,7 @@ Http::delete('/v1/projects/:projectId/templates/sms/:type/:locale')
]), Response::MODEL_SMS_TEMPLATE);
});
-Http::delete('/v1/projects/:projectId/templates/email/:type/:locale')
+App::delete('/v1/projects/:projectId/templates/email/:type/:locale')
->desc('Reset custom email template')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
@@ -2034,6 +2028,7 @@ Http::delete('/v1/projects/:projectId/templates/email/:type/:locale')
->inject('response')
->inject('dbForConsole')
->action(function (string $projectId, string $type, string $locale, Response $response, Database $dbForConsole) {
+
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
@@ -2041,7 +2036,7 @@ Http::delete('/v1/projects/:projectId/templates/email/:type/:locale')
}
$templates = $project->getAttribute('templates', []);
- $template = $templates['email.' . $type . '-' . $locale] ?? null;
+ $template = $templates['email.' . $type . '-' . $locale] ?? null;
if (is_null($template)) {
throw new Exception(Exception::PROJECT_TEMPLATE_DEFAULT_DELETION);
diff --git a/app/controllers/api/proxy.php b/app/controllers/api/proxy.php
index 326c164dd3..84484a7209 100644
--- a/app/controllers/api/proxy.php
+++ b/app/controllers/api/proxy.php
@@ -7,6 +7,7 @@ use Appwrite\Extend\Exception;
use Appwrite\Network\Validator\CNAME;
use Appwrite\Utopia\Database\Validator\Queries\Rules;
use Appwrite\Utopia\Response;
+use Utopia\App;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Exception\Query as QueryException;
@@ -14,16 +15,15 @@ use Utopia\Database\Helpers\ID;
use Utopia\Database\Query;
use Utopia\Database\Validator\UID;
use Utopia\Domains\Domain;
-use Utopia\Http\Http;
-use Utopia\Http\Validator\Domain as ValidatorDomain;
-use Utopia\Http\Validator\Text;
-use Utopia\Http\Validator\WhiteList;
use Utopia\Logger\Log;
use Utopia\System\System;
+use Utopia\Validator\Domain as ValidatorDomain;
+use Utopia\Validator\Text;
+use Utopia\Validator\WhiteList;
-Http::post('/v1/proxy/rules')
+App::post('/v1/proxy/rules')
->groups(['api', 'proxy'])
- ->desc('Create Rule')
+ ->desc('Create rule')
->label('scope', 'rules.write')
->label('event', 'rules.[ruleId].create')
->label('audits.event', 'rule.create')
@@ -147,9 +147,9 @@ Http::post('/v1/proxy/rules')
->dynamic($rule, Response::MODEL_PROXY_RULE);
});
-Http::get('/v1/proxy/rules')
+App::get('/v1/proxy/rules')
->groups(['api', 'proxy'])
- ->desc('List Rules')
+ ->desc('List rules')
->label('scope', 'rules.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'proxy')
@@ -210,9 +210,9 @@ Http::get('/v1/proxy/rules')
]), Response::MODEL_PROXY_RULE_LIST);
});
-Http::get('/v1/proxy/rules/:ruleId')
+App::get('/v1/proxy/rules/:ruleId')
->groups(['api', 'proxy'])
- ->desc('Get Rule')
+ ->desc('Get rule')
->label('scope', 'rules.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'proxy')
@@ -239,9 +239,9 @@ Http::get('/v1/proxy/rules/:ruleId')
$response->dynamic($rule, Response::MODEL_PROXY_RULE);
});
-Http::delete('/v1/proxy/rules/:ruleId')
+App::delete('/v1/proxy/rules/:ruleId')
->groups(['api', 'proxy'])
- ->desc('Delete Rule')
+ ->desc('Delete rule')
->label('scope', 'rules.write')
->label('event', 'rules.[ruleId].delete')
->label('audits.event', 'rules.delete')
@@ -276,8 +276,8 @@ Http::delete('/v1/proxy/rules/:ruleId')
$response->noContent();
});
-Http::patch('/v1/proxy/rules/:ruleId/verification')
- ->desc('Update Rule Verification Status')
+App::patch('/v1/proxy/rules/:ruleId/verification')
+ ->desc('Update rule verification status')
->groups(['api', 'proxy'])
->label('scope', 'rules.write')
->label('event', 'rules.[ruleId].update')
diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php
index e0408acb37..4e30832a67 100644
--- a/app/controllers/api/storage.php
+++ b/app/controllers/api/storage.php
@@ -11,8 +11,8 @@ use Appwrite\OpenSSL\OpenSSL;
use Appwrite\Utopia\Database\Validator\CustomId;
use Appwrite\Utopia\Database\Validator\Queries\Buckets;
use Appwrite\Utopia\Database\Validator\Queries\Files;
-use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response;
+use Utopia\App;
use Utopia\Config\Config;
use Utopia\Database\Database;
use Utopia\Database\Document;
@@ -23,16 +23,8 @@ use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
-use Utopia\Database\Validator\Authorization\Input;
use Utopia\Database\Validator\Permissions;
use Utopia\Database\Validator\UID;
-use Utopia\Http\Http;
-use Utopia\Http\Validator\ArrayList;
-use Utopia\Http\Validator\Boolean;
-use Utopia\Http\Validator\HexColor;
-use Utopia\Http\Validator\Range;
-use Utopia\Http\Validator\Text;
-use Utopia\Http\Validator\WhiteList;
use Utopia\Image\Image;
use Utopia\Storage\Compression\Algorithms\GZIP;
use Utopia\Storage\Compression\Algorithms\Zstd;
@@ -43,9 +35,16 @@ use Utopia\Storage\Validator\File;
use Utopia\Storage\Validator\FileExt;
use Utopia\Storage\Validator\FileSize;
use Utopia\Storage\Validator\Upload;
+use Utopia\Swoole\Request;
use Utopia\System\System;
+use Utopia\Validator\ArrayList;
+use Utopia\Validator\Boolean;
+use Utopia\Validator\HexColor;
+use Utopia\Validator\Range;
+use Utopia\Validator\Text;
+use Utopia\Validator\WhiteList;
-Http::post('/v1/storage/buckets')
+App::post('/v1/storage/buckets')
->desc('Create bucket')
->groups(['api', 'storage'])
->label('scope', 'buckets.write')
@@ -143,7 +142,7 @@ Http::post('/v1/storage/buckets')
->dynamic($bucket, Response::MODEL_BUCKET);
});
-Http::get('/v1/storage/buckets')
+App::get('/v1/storage/buckets')
->desc('List buckets')
->groups(['api', 'storage'])
->label('scope', 'buckets.read')
@@ -197,7 +196,7 @@ Http::get('/v1/storage/buckets')
]), Response::MODEL_BUCKET_LIST);
});
-Http::get('/v1/storage/buckets/:bucketId')
+App::get('/v1/storage/buckets/:bucketId')
->desc('Get bucket')
->groups(['api', 'storage'])
->label('scope', 'buckets.read')
@@ -222,7 +221,7 @@ Http::get('/v1/storage/buckets/:bucketId')
$response->dynamic($bucket, Response::MODEL_BUCKET);
});
-Http::put('/v1/storage/buckets/:bucketId')
+App::put('/v1/storage/buckets/:bucketId')
->desc('Update bucket')
->groups(['api', 'storage'])
->label('scope', 'buckets.write')
@@ -289,7 +288,7 @@ Http::put('/v1/storage/buckets/:bucketId')
$response->dynamic($bucket, Response::MODEL_BUCKET);
});
-Http::delete('/v1/storage/buckets/:bucketId')
+App::delete('/v1/storage/buckets/:bucketId')
->desc('Delete bucket')
->groups(['api', 'storage'])
->label('scope', 'buckets.write')
@@ -330,7 +329,7 @@ Http::delete('/v1/storage/buckets/:bucketId')
$response->noContent();
});
-Http::post('/v1/storage/buckets/:bucketId/files')
+App::post('/v1/storage/buckets/:bucketId/files')
->alias('/v1/storage/files', ['bucketId' => 'default'])
->desc('Create file')
->groups(['api', 'storage'])
@@ -362,19 +361,19 @@ Http::post('/v1/storage/buckets/:bucketId/files')
->inject('mode')
->inject('deviceForFiles')
->inject('deviceForLocal')
- ->inject('authorization')
- ->action(function (string $bucketId, string $fileId, mixed $file, ?array $permissions, Request $request, Response $response, Database $dbForProject, Document $user, Event $queueForEvents, string $mode, Device $deviceForFiles, Device $deviceForLocal, Authorization $authorization) {
+ ->action(function (string $bucketId, string $fileId, mixed $file, ?array $permissions, Request $request, Response $response, Database $dbForProject, Document $user, Event $queueForEvents, string $mode, Device $deviceForFiles, Device $deviceForLocal) {
- $bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
+ $bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
- $isAPIKey = Auth::isAppUser($authorization->getRoles());
- $isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles());
+ $isAPIKey = Auth::isAppUser(Authorization::getRoles());
+ $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) {
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
}
- if (!$authorization->isValid(new Input(Database::PERMISSION_CREATE, $bucket->getCreate()))) {
+ $validator = new Authorization(Database::PERMISSION_CREATE);
+ if (!$validator->isValid($bucket->getCreate())) {
throw new Exception(Exception::USER_UNAUTHORIZED);
}
@@ -398,7 +397,7 @@ Http::post('/v1/storage/buckets/:bucketId/files')
}
// Users can only manage their own roles, API keys and Admin users can manage any
- $roles = $authorization->getRoles();
+ $roles = Authorization::getRoles();
if (!Auth::isAppUser($roles) && !Auth::isPrivilegedUser($roles)) {
foreach (Database::PERMISSIONS as $type) {
foreach ($permissions as $permission) {
@@ -411,7 +410,7 @@ Http::post('/v1/storage/buckets/:bucketId/files')
$permission->getIdentifier(),
$permission->getDimension()
))->toString();
- if (!$authorization->isRole($role)) {
+ if (!Authorization::isRole($role)) {
throw new Exception(Exception::USER_UNAUTHORIZED, 'Permissions must be one of: (' . \implode(', ', $roles) . ')');
}
}
@@ -631,10 +630,11 @@ Http::post('/v1/storage/buckets/:bucketId/files')
* However as with chunk upload even if we are updating, we are essentially creating a file
* adding it's new chunk so we validate create permission instead of update
*/
- if (!$authorization->isValid(new Input(Database::PERMISSION_CREATE, $bucket->getCreate()))) {
+ $validator = new Authorization(Database::PERMISSION_CREATE);
+ if (!$validator->isValid($bucket->getCreate())) {
throw new Exception(Exception::USER_UNAUTHORIZED);
}
- $file = $authorization->skip(fn () => $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file));
+ $file = Authorization::skip(fn () => $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file));
}
} else {
if ($file->isEmpty()) {
@@ -669,10 +669,11 @@ Http::post('/v1/storage/buckets/:bucketId/files')
* However as with chunk upload even if we are updating, we are essentially creating a file
* adding it's new chunk so we validate create permission instead of update
*/
- if (!$authorization->isValid(new Input(Database::PERMISSION_CREATE, $bucket->getCreate()))) {
+ $validator = new Authorization(Database::PERMISSION_CREATE);
+ if (!$validator->isValid($bucket->getCreate())) {
throw new Exception(Exception::USER_UNAUTHORIZED);
}
- $file = $authorization->skip(fn () => $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file));
+ $file = Authorization::skip(fn () => $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file));
}
}
@@ -689,7 +690,7 @@ Http::post('/v1/storage/buckets/:bucketId/files')
->dynamic($file, Response::MODEL_FILE);
});
-Http::get('/v1/storage/buckets/:bucketId/files')
+App::get('/v1/storage/buckets/:bucketId/files')
->alias('/v1/storage/files', ['bucketId' => 'default'])
->desc('List files')
->groups(['api', 'storage'])
@@ -707,19 +708,19 @@ Http::get('/v1/storage/buckets/:bucketId/files')
->inject('response')
->inject('dbForProject')
->inject('mode')
- ->inject('authorization')
- ->action(function (string $bucketId, array $queries, string $search, Response $response, Database $dbForProject, string $mode, Authorization $authorization) {
- $bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
+ ->action(function (string $bucketId, array $queries, string $search, Response $response, Database $dbForProject, string $mode) {
+ $bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
- $isAPIKey = Auth::isAppUser($authorization->getRoles());
- $isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles());
+ $isAPIKey = Auth::isAppUser(Authorization::getRoles());
+ $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) {
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
}
$fileSecurity = $bucket->getAttribute('fileSecurity', false);
- $valid = $authorization->isValid(new Input(Database::PERMISSION_READ, $bucket->getRead()));
+ $validator = new Authorization(Database::PERMISSION_READ);
+ $valid = $validator->isValid($bucket->getRead());
if (!$fileSecurity && !$valid) {
throw new Exception(Exception::USER_UNAUTHORIZED);
}
@@ -748,7 +749,7 @@ Http::get('/v1/storage/buckets/:bucketId/files')
if ($fileSecurity && !$valid) {
$cursorDocument = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
} else {
- $cursorDocument = $authorization->skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
+ $cursorDocument = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
}
if ($cursorDocument->isEmpty()) {
@@ -764,8 +765,8 @@ Http::get('/v1/storage/buckets/:bucketId/files')
$files = $dbForProject->find('bucket_' . $bucket->getInternalId(), $queries);
$total = $dbForProject->count('bucket_' . $bucket->getInternalId(), $filterQueries, APP_LIMIT_COUNT);
} else {
- $files = $authorization->skip(fn () => $dbForProject->find('bucket_' . $bucket->getInternalId(), $queries));
- $total = $authorization->skip(fn () => $dbForProject->count('bucket_' . $bucket->getInternalId(), $filterQueries, APP_LIMIT_COUNT));
+ $files = Authorization::skip(fn () => $dbForProject->find('bucket_' . $bucket->getInternalId(), $queries));
+ $total = Authorization::skip(fn () => $dbForProject->count('bucket_' . $bucket->getInternalId(), $filterQueries, APP_LIMIT_COUNT));
}
$response->dynamic(new Document([
@@ -774,7 +775,7 @@ Http::get('/v1/storage/buckets/:bucketId/files')
]), Response::MODEL_FILE_LIST);
});
-Http::get('/v1/storage/buckets/:bucketId/files/:fileId')
+App::get('/v1/storage/buckets/:bucketId/files/:fileId')
->alias('/v1/storage/files/:fileId', ['bucketId' => 'default'])
->desc('Get file')
->groups(['api', 'storage'])
@@ -791,19 +792,19 @@ Http::get('/v1/storage/buckets/:bucketId/files/:fileId')
->inject('response')
->inject('dbForProject')
->inject('mode')
- ->inject('authorization')
- ->action(function (string $bucketId, string $fileId, Response $response, Database $dbForProject, string $mode, Authorization $authorization) {
- $bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
+ ->action(function (string $bucketId, string $fileId, Response $response, Database $dbForProject, string $mode) {
+ $bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
- $isAPIKey = Auth::isAppUser($authorization->getRoles());
- $isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles());
+ $isAPIKey = Auth::isAppUser(Authorization::getRoles());
+ $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) {
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
}
$fileSecurity = $bucket->getAttribute('fileSecurity', false);
- $valid = $authorization->isValid(new Input(Database::PERMISSION_READ, $bucket->getRead()));
+ $validator = new Authorization(Database::PERMISSION_READ);
+ $valid = $validator->isValid($bucket->getRead());
if (!$fileSecurity && !$valid) {
throw new Exception(Exception::USER_UNAUTHORIZED);
}
@@ -811,7 +812,7 @@ Http::get('/v1/storage/buckets/:bucketId/files/:fileId')
if ($fileSecurity && !$valid) {
$file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
} else {
- $file = $authorization->skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
+ $file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
}
if ($file->isEmpty()) {
@@ -821,7 +822,7 @@ Http::get('/v1/storage/buckets/:bucketId/files/:fileId')
$response->dynamic($file, Response::MODEL_FILE);
});
-Http::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
+App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
->alias('/v1/storage/files/:fileId/preview', ['bucketId' => 'default'])
->desc('Get file preview')
->groups(['api', 'storage'])
@@ -856,24 +857,24 @@ Http::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
->inject('mode')
->inject('deviceForFiles')
->inject('deviceForLocal')
- ->inject('authorization')
- ->action(function (string $bucketId, string $fileId, int $width, int $height, string $gravity, int $quality, int $borderWidth, string $borderColor, int $borderRadius, float $opacity, int $rotation, string $background, string $output, Request $request, Response $response, Document $project, Database $dbForProject, string $mode, Device $deviceForFiles, Device $deviceForLocal, Authorization $authorization) {
+ ->action(function (string $bucketId, string $fileId, int $width, int $height, string $gravity, int $quality, int $borderWidth, string $borderColor, int $borderRadius, float $opacity, int $rotation, string $background, string $output, Request $request, Response $response, Document $project, Database $dbForProject, string $mode, Device $deviceForFiles, Device $deviceForLocal) {
if (!\extension_loaded('imagick')) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Imagick extension is missing');
}
- $bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
+ $bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
- $isAPIKey = Auth::isAppUser($authorization->getRoles());
- $isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles());
+ $isAPIKey = Auth::isAppUser(Authorization::getRoles());
+ $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) {
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
}
$fileSecurity = $bucket->getAttribute('fileSecurity', false);
- $valid = $authorization->isValid(new Input(Database::PERMISSION_READ, $bucket->getRead()));
+ $validator = new Authorization(Database::PERMISSION_READ);
+ $valid = $validator->isValid($bucket->getRead());
if (!$fileSecurity && !$valid) {
throw new Exception(Exception::USER_UNAUTHORIZED);
}
@@ -881,17 +882,13 @@ Http::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
if ($fileSecurity && !$valid) {
$file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
} else {
- $file = $authorization->skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
+ $file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
}
if ($file->isEmpty()) {
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
}
- if ((\strpos($request->getAccept(), 'image/webp') === false) && ('webp' === $output)) { // Fallback webp to jpeg when no browser support
- $output = 'jpg';
- }
-
$inputs = Config::getParam('storage-inputs');
$outputs = Config::getParam('storage-outputs');
$fileLogos = Config::getParam('storage-logos');
@@ -997,7 +994,7 @@ Http::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
unset($image);
});
-Http::get('/v1/storage/buckets/:bucketId/files/:fileId/download')
+App::get('/v1/storage/buckets/:bucketId/files/:fileId/download')
->alias('/v1/storage/files/:fileId/download', ['bucketId' => 'default'])
->desc('Get file for download')
->groups(['api', 'storage'])
@@ -1016,20 +1013,20 @@ Http::get('/v1/storage/buckets/:bucketId/files/:fileId/download')
->inject('dbForProject')
->inject('mode')
->inject('deviceForFiles')
- ->inject('authorization')
- ->action(function (string $bucketId, string $fileId, Request $request, Response $response, Database $dbForProject, string $mode, Device $deviceForFiles, Authorization $authorization) {
+ ->action(function (string $bucketId, string $fileId, Request $request, Response $response, Database $dbForProject, string $mode, Device $deviceForFiles) {
- $bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
+ $bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
- $isAPIKey = Auth::isAppUser($authorization->getRoles());
- $isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles());
+ $isAPIKey = Auth::isAppUser(Authorization::getRoles());
+ $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) {
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
}
$fileSecurity = $bucket->getAttribute('fileSecurity', false);
- $valid = $authorization->isValid(new Input(Database::PERMISSION_READ, $bucket->getRead()));
+ $validator = new Authorization(Database::PERMISSION_READ);
+ $valid = $validator->isValid($bucket->getRead());
if (!$fileSecurity && !$valid) {
throw new Exception(Exception::USER_UNAUTHORIZED);
}
@@ -1037,7 +1034,7 @@ Http::get('/v1/storage/buckets/:bucketId/files/:fileId/download')
if ($fileSecurity && !$valid) {
$file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
} else {
- $file = $authorization->skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
+ $file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
}
if ($file->isEmpty()) {
@@ -1137,7 +1134,7 @@ Http::get('/v1/storage/buckets/:bucketId/files/:fileId/download')
}
});
-Http::get('/v1/storage/buckets/:bucketId/files/:fileId/view')
+App::get('/v1/storage/buckets/:bucketId/files/:fileId/view')
->alias('/v1/storage/files/:fileId/view', ['bucketId' => 'default'])
->desc('Get file for view')
->groups(['api', 'storage'])
@@ -1156,19 +1153,19 @@ Http::get('/v1/storage/buckets/:bucketId/files/:fileId/view')
->inject('dbForProject')
->inject('mode')
->inject('deviceForFiles')
- ->inject('authorization')
- ->action(function (string $bucketId, string $fileId, Response $response, Request $request, Database $dbForProject, string $mode, Device $deviceForFiles, Authorization $authorization) {
- $bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
+ ->action(function (string $bucketId, string $fileId, Response $response, Request $request, Database $dbForProject, string $mode, Device $deviceForFiles) {
+ $bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
- $isAPIKey = Auth::isAppUser($authorization->getRoles());
- $isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles());
+ $isAPIKey = Auth::isAppUser(Authorization::getRoles());
+ $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) {
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
}
$fileSecurity = $bucket->getAttribute('fileSecurity', false);
- $valid = $authorization->isValid(new Input(Database::PERMISSION_READ, $bucket->getRead()));
+ $validator = new Authorization(Database::PERMISSION_READ);
+ $valid = $validator->isValid($bucket->getRead());
if (!$fileSecurity && !$valid) {
throw new Exception(Exception::USER_UNAUTHORIZED);
}
@@ -1176,7 +1173,7 @@ Http::get('/v1/storage/buckets/:bucketId/files/:fileId/view')
if ($fileSecurity && !$valid) {
$file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
} else {
- $file = $authorization->skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
+ $file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
}
if ($file->isEmpty()) {
@@ -1289,7 +1286,7 @@ Http::get('/v1/storage/buckets/:bucketId/files/:fileId/view')
}
});
-Http::get('/v1/storage/buckets/:bucketId/files/:fileId/push')
+App::get('/v1/storage/buckets/:bucketId/files/:fileId/push')
->desc('Get file for push notification')
->groups(['api', 'storage'])
->label('scope', 'public')
@@ -1305,9 +1302,8 @@ Http::get('/v1/storage/buckets/:bucketId/files/:fileId/push')
->inject('project')
->inject('mode')
->inject('deviceForFiles')
- ->inject('authorization')
- ->action(function (string $bucketId, string $fileId, string $jwt, Response $response, Request $request, Database $dbForProject, Document $project, string $mode, Device $deviceForFiles, Authorization $authorization) {
- $bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
+ ->action(function (string $bucketId, string $fileId, string $jwt, Response $response, Request $request, Database $dbForProject, Document $project, string $mode, Device $deviceForFiles) {
+ $bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
$decoder = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 3600, 0);
@@ -1325,14 +1321,14 @@ Http::get('/v1/storage/buckets/:bucketId/files/:fileId/push')
throw new Exception(Exception::USER_UNAUTHORIZED);
}
- $isAPIKey = Auth::isAppUser($authorization->getRoles());
- $isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles());
+ $isAPIKey = Auth::isAppUser(Authorization::getRoles());
+ $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) {
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
}
- $file = $authorization->skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
+ $file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
if ($file->isEmpty()) {
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
@@ -1443,7 +1439,7 @@ Http::get('/v1/storage/buckets/:bucketId/files/:fileId/push')
}
});
-Http::put('/v1/storage/buckets/:bucketId/files/:fileId')
+App::put('/v1/storage/buckets/:bucketId/files/:fileId')
->alias('/v1/storage/files/:fileId', ['bucketId' => 'default'])
->desc('Update file')
->groups(['api', 'storage'])
@@ -1470,26 +1466,26 @@ Http::put('/v1/storage/buckets/:bucketId/files/:fileId')
->inject('user')
->inject('mode')
->inject('queueForEvents')
- ->inject('authorization')
- ->action(function (string $bucketId, string $fileId, ?string $name, ?array $permissions, Response $response, Database $dbForProject, Document $user, string $mode, Event $queueForEvents, Authorization $authorization) {
+ ->action(function (string $bucketId, string $fileId, ?string $name, ?array $permissions, Response $response, Database $dbForProject, Document $user, string $mode, Event $queueForEvents) {
- $bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
+ $bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
- $isAPIKey = Auth::isAppUser($authorization->getRoles());
- $isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles());
+ $isAPIKey = Auth::isAppUser(Authorization::getRoles());
+ $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) {
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
}
$fileSecurity = $bucket->getAttribute('fileSecurity', false);
- $valid = $authorization->isValid(new Input(Database::PERMISSION_UPDATE, $bucket->getUpdate()));
+ $validator = new Authorization(Database::PERMISSION_UPDATE);
+ $valid = $validator->isValid($bucket->getUpdate());
if (!$fileSecurity && !$valid) {
throw new Exception(Exception::USER_UNAUTHORIZED);
}
// Read permission should not be required for update
- $file = $authorization->skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
+ $file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
if ($file->isEmpty()) {
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
@@ -1503,7 +1499,7 @@ Http::put('/v1/storage/buckets/:bucketId/files/:fileId')
]);
// Users can only manage their own roles, API keys and Admin users can manage any
- $roles = $authorization->getRoles();
+ $roles = Authorization::getRoles();
if (!Auth::isAppUser($roles) && !Auth::isPrivilegedUser($roles) && !\is_null($permissions)) {
foreach (Database::PERMISSIONS as $type) {
foreach ($permissions as $permission) {
@@ -1516,7 +1512,7 @@ Http::put('/v1/storage/buckets/:bucketId/files/:fileId')
$permission->getIdentifier(),
$permission->getDimension()
))->toString();
- if (!$authorization->isRole($role)) {
+ if (!Authorization::isRole($role)) {
throw new Exception(Exception::USER_UNAUTHORIZED, 'Permissions must be one of: (' . \implode(', ', $roles) . ')');
}
}
@@ -1536,7 +1532,7 @@ Http::put('/v1/storage/buckets/:bucketId/files/:fileId')
if ($fileSecurity && !$valid) {
$file = $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file);
} else {
- $file = $authorization->skip(fn () => $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file));
+ $file = Authorization::skip(fn () => $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file));
}
$queueForEvents
@@ -1548,8 +1544,8 @@ Http::put('/v1/storage/buckets/:bucketId/files/:fileId')
$response->dynamic($file, Response::MODEL_FILE);
});
-Http::delete('/v1/storage/buckets/:bucketId/files/:fileId')
- ->desc('Delete File')
+App::delete('/v1/storage/buckets/:bucketId/files/:fileId')
+ ->desc('Delete file')
->groups(['api', 'storage'])
->label('scope', 'files.write')
->label('event', 'buckets.[bucketId].files.[fileId].delete')
@@ -1572,32 +1568,32 @@ Http::delete('/v1/storage/buckets/:bucketId/files/:fileId')
->inject('mode')
->inject('deviceForFiles')
->inject('queueForDeletes')
- ->inject('authorization')
- ->action(function (string $bucketId, string $fileId, Response $response, Database $dbForProject, Event $queueForEvents, string $mode, Device $deviceForFiles, Delete $queueForDeletes, Authorization $authorization) {
- $bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
+ ->action(function (string $bucketId, string $fileId, Response $response, Database $dbForProject, Event $queueForEvents, string $mode, Device $deviceForFiles, Delete $queueForDeletes) {
+ $bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
- $isAPIKey = Auth::isAppUser($authorization->getRoles());
- $isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles());
+ $isAPIKey = Auth::isAppUser(Authorization::getRoles());
+ $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) {
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
}
$fileSecurity = $bucket->getAttribute('fileSecurity', false);
- $valid = $authorization->isValid(new Input(Database::PERMISSION_DELETE, $bucket->getDelete()));
+ $validator = new Authorization(Database::PERMISSION_DELETE);
+ $valid = $validator->isValid($bucket->getDelete());
if (!$fileSecurity && !$valid) {
throw new Exception(Exception::USER_UNAUTHORIZED);
}
// Read permission should not be required for delete
- $file = $authorization->skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
+ $file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
if ($file->isEmpty()) {
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
}
// Make sure we don't delete the file before the document permission check occurs
- if ($fileSecurity && !$valid && !$authorization->isValid(new Input(Database::PERMISSION_DELETE, $file->getDelete()))) {
+ if ($fileSecurity && !$valid && !$validator->isValid($file->getDelete())) {
throw new Exception(Exception::USER_UNAUTHORIZED);
}
@@ -1621,7 +1617,7 @@ Http::delete('/v1/storage/buckets/:bucketId/files/:fileId')
if ($fileSecurity && !$valid) {
$deleted = $dbForProject->deleteDocument('bucket_' . $bucket->getInternalId(), $fileId);
} else {
- $deleted = $authorization->skip(fn () => $dbForProject->deleteDocument('bucket_' . $bucket->getInternalId(), $fileId));
+ $deleted = Authorization::skip(fn () => $dbForProject->deleteDocument('bucket_' . $bucket->getInternalId(), $fileId));
}
if (!$deleted) {
@@ -1641,7 +1637,7 @@ Http::delete('/v1/storage/buckets/:bucketId/files/:fileId')
$response->noContent();
});
-Http::get('/v1/storage/usage')
+App::get('/v1/storage/usage')
->desc('Get storage usage stats')
->groups(['api', 'storage'])
->label('scope', 'files.read')
@@ -1654,8 +1650,7 @@ Http::get('/v1/storage/usage')
->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), 'Date range.', true)
->inject('response')
->inject('dbForProject')
- ->inject('authorization')
- ->action(function (string $range, Response $response, Database $dbForProject, Authorization $authorization) {
+ ->action(function (string $range, Response $response, Database $dbForProject) {
$periods = Config::getParam('usage', []);
$stats = $usage = [];
@@ -1667,7 +1662,7 @@ Http::get('/v1/storage/usage')
];
$total = [];
- $authorization->skip(function () use ($dbForProject, $days, $metrics, &$stats, &$total) {
+ Authorization::skip(function () use ($dbForProject, $days, $metrics, &$stats, &$total) {
foreach ($metrics as $metric) {
$result = $dbForProject->findOne('stats', [
Query::equal('metric', [$metric]),
@@ -1721,7 +1716,7 @@ Http::get('/v1/storage/usage')
]), Response::MODEL_USAGE_STORAGE);
});
-Http::get('/v1/storage/:bucketId/usage')
+App::get('/v1/storage/:bucketId/usage')
->desc('Get bucket usage stats')
->groups(['api', 'storage'])
->label('scope', 'files.read')
@@ -1735,8 +1730,7 @@ Http::get('/v1/storage/:bucketId/usage')
->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), 'Date range.', true)
->inject('response')
->inject('dbForProject')
- ->inject('authorization')
- ->action(function (string $bucketId, string $range, Response $response, Database $dbForProject, Authorization $authorization) {
+ ->action(function (string $bucketId, string $range, Response $response, Database $dbForProject) {
$bucket = $dbForProject->getDocument('buckets', $bucketId);
@@ -1752,7 +1746,8 @@ Http::get('/v1/storage/:bucketId/usage')
str_replace('{bucketInternalId}', $bucket->getInternalId(), METRIC_BUCKET_ID_FILES_STORAGE),
];
- $authorization->skip(function () use ($dbForProject, $days, $metrics, &$stats, &$total) {
+
+ Authorization::skip(function () use ($dbForProject, $days, $metrics, &$stats, &$total) {
foreach ($metrics as $metric) {
$result = $dbForProject->findOne('stats', [
Query::equal('metric', [$metric]),
diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php
index ed49ba012f..e5910fa6ec 100644
--- a/app/controllers/api/teams.php
+++ b/app/controllers/api/teams.php
@@ -1,7 +1,6 @@
desc('Create team')
->groups(['api', 'teams'])
->label('event', 'teams.[teamId].create')
@@ -66,16 +66,15 @@ Http::post('/v1/teams')
->inject('user')
->inject('dbForProject')
->inject('queueForEvents')
- ->inject('authorization')
- ->action(function (string $teamId, string $name, array $roles, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Authorization $authorization) {
+ ->action(function (string $teamId, string $name, array $roles, Response $response, Document $user, Database $dbForProject, Event $queueForEvents) {
- $isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles());
- $isAppUser = Auth::isAppUser($authorization->getRoles());
+ $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
+ $isAppUser = Auth::isAppUser(Authorization::getRoles());
$teamId = $teamId == 'unique()' ? ID::unique() : $teamId;
try {
- $team = $authorization->skip(fn () => $dbForProject->createDocument('teams', new Document([
+ $team = Authorization::skip(fn () => $dbForProject->createDocument('teams', new Document([
'$id' => $teamId,
'$permissions' => [
Permission::read(Role::team($teamId)),
@@ -134,7 +133,7 @@ Http::post('/v1/teams')
->dynamic($team, Response::MODEL_TEAM);
});
-Http::get('/v1/teams')
+App::get('/v1/teams')
->desc('List teams')
->groups(['api', 'teams'])
->label('scope', 'teams.read')
@@ -192,7 +191,7 @@ Http::get('/v1/teams')
]), Response::MODEL_TEAM_LIST);
});
-Http::get('/v1/teams/:teamId')
+App::get('/v1/teams/:teamId')
->desc('Get team')
->groups(['api', 'teams'])
->label('scope', 'teams.read')
@@ -219,7 +218,7 @@ Http::get('/v1/teams/:teamId')
$response->dynamic($team, Response::MODEL_TEAM);
});
-Http::get('/v1/teams/:teamId/prefs')
+App::get('/v1/teams/:teamId/prefs')
->desc('Get team preferences')
->groups(['api', 'teams'])
->label('scope', 'teams.read')
@@ -247,7 +246,7 @@ Http::get('/v1/teams/:teamId/prefs')
$response->dynamic(new Document($prefs), Response::MODEL_PREFERENCES);
});
-Http::put('/v1/teams/:teamId')
+App::put('/v1/teams/:teamId')
->desc('Update name')
->groups(['api', 'teams'])
->label('event', 'teams.[teamId].update')
@@ -290,7 +289,7 @@ Http::put('/v1/teams/:teamId')
$response->dynamic($team, Response::MODEL_TEAM);
});
-Http::put('/v1/teams/:teamId/prefs')
+App::put('/v1/teams/:teamId/prefs')
->desc('Update preferences')
->groups(['api', 'teams'])
->label('event', 'teams.[teamId].update.prefs')
@@ -326,7 +325,7 @@ Http::put('/v1/teams/:teamId/prefs')
$response->dynamic(new Document($prefs), Response::MODEL_PREFERENCES);
});
-Http::delete('/v1/teams/:teamId')
+App::delete('/v1/teams/:teamId')
->desc('Delete team')
->groups(['api', 'teams'])
->label('event', 'teams.[teamId].delete')
@@ -375,7 +374,7 @@ Http::delete('/v1/teams/:teamId')
$response->noContent();
});
-Http::post('/v1/teams/:teamId/memberships')
+App::post('/v1/teams/:teamId/memberships')
->desc('Create team membership')
->groups(['api', 'teams', 'auth'])
->label('event', 'teams.[teamId].memberships.[membershipId].create')
@@ -396,7 +395,17 @@ Http::post('/v1/teams/:teamId/memberships')
->param('email', '', new Email(), 'Email of the new team member.', true)
->param('userId', '', new UID(), 'ID of the user to be added to a team.', true)
->param('phone', '', new Phone(), 'Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.', true)
- ->param('roles', [], new ArrayList(new Key(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of strings. Use this param to set the user roles in the team. A role can be any string. Learn more about [roles and permissions](https://appwrite.io/docs/permissions). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 32 characters long.')
+ ->param('roles', [], function (Document $project) {
+ if ($project->getId() === 'console') {
+ ;
+ $roles = array_keys(Config::getParam('roles', []));
+ array_filter($roles, function ($role) {
+ return !in_array($role, [Auth::USER_ROLE_APPS, Auth::USER_ROLE_GUESTS, Auth::USER_ROLE_USERS]);
+ });
+ return new ArrayList(new WhiteList($roles), APP_LIMIT_ARRAY_PARAMS_SIZE);
+ }
+ return new ArrayList(new Key(), APP_LIMIT_ARRAY_PARAMS_SIZE);
+ }, 'Array of strings. Use this param to set the user roles in the team. A role can be any string. Learn more about [roles and permissions](https://appwrite.io/docs/permissions). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 32 characters long.', false, ['project'])
->param('url', '', fn ($clients) => new Host($clients), 'URL to redirect the user back to your app from the invitation email. This parameter is not required when an API key is supplied. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['clients']) // TODO add our own built-in confirm page
->param('name', '', new Text(128), 'Name of the new team member. Max length: 128 chars.', true)
->inject('response')
@@ -407,10 +416,9 @@ Http::post('/v1/teams/:teamId/memberships')
->inject('queueForMails')
->inject('queueForMessaging')
->inject('queueForEvents')
- ->inject('authorization')
- ->action(function (string $teamId, string $email, string $userId, string $phone, array $roles, string $url, string $name, Response $response, Document $project, Document $user, Database $dbForProject, Locale $locale, Mail $queueForMails, Messaging $queueForMessaging, Event $queueForEvents, Authorization $authorization) {
- $isAPIKey = Auth::isAppUser($authorization->getRoles());
- $isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles());
+ ->action(function (string $teamId, string $email, string $userId, string $phone, array $roles, string $url, string $name, Response $response, Document $project, Document $user, Database $dbForProject, Locale $locale, Mail $queueForMails, Messaging $queueForMessaging, Event $queueForEvents) {
+ $isAPIKey = Auth::isAppUser(Authorization::getRoles());
+ $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
$url = htmlentities($url);
if (empty($url)) {
@@ -422,8 +430,8 @@ Http::post('/v1/teams/:teamId/memberships')
if (empty($userId) && empty($email) && empty($phone)) {
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'At least one of userId, email, or phone is required');
}
- $isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles());
- $isAppUser = Auth::isAppUser($authorization->getRoles());
+ $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
+ $isAppUser = Auth::isAppUser(Authorization::getRoles());
if (!$isPrivilegedUser && !$isAppUser && empty(System::getEnv('_APP_SMTP_HOST'))) {
throw new Exception(Exception::GENERAL_SMTP_DISABLED);
@@ -483,7 +491,7 @@ Http::post('/v1/teams/:teamId/memberships')
try {
$userId = ID::unique();
- $invitee = $authorization->skip(fn () => $dbForProject->createDocument('users', new Document([
+ $invitee = Authorization::skip(fn () => $dbForProject->createDocument('users', new Document([
'$id' => $userId,
'$permissions' => [
Permission::read(Role::any()),
@@ -519,7 +527,7 @@ Http::post('/v1/teams/:teamId/memberships')
}
}
- $isOwner = $authorization->isRole('team:' . $team->getId() . '/owner');
+ $isOwner = Authorization::isRole('team:' . $team->getId() . '/owner');
if (!$isOwner && !$isPrivilegedUser && !$isAppUser) { // Not owner, not admin, not app (server)
throw new Exception(Exception::USER_UNAUTHORIZED, 'User is not allowed to send invitations for this team');
@@ -551,12 +559,12 @@ Http::post('/v1/teams/:teamId/memberships')
if ($isPrivilegedUser || $isAppUser) { // Allow admin to create membership
try {
- $membership = $authorization->skip(fn () => $dbForProject->createDocument('memberships', $membership));
+ $membership = Authorization::skip(fn () => $dbForProject->createDocument('memberships', $membership));
} catch (Duplicate $th) {
throw new Exception(Exception::TEAM_INVITE_ALREADY_EXISTS);
}
- $authorization->skip(fn () => $dbForProject->increaseDocumentAttribute('teams', $team->getId(), 'total', 1));
+ Authorization::skip(fn () => $dbForProject->increaseDocumentAttribute('teams', $team->getId(), 'total', 1));
$dbForProject->purgeCachedDocument('users', $invitee->getId());
} else {
@@ -578,7 +586,7 @@ Http::post('/v1/teams/:teamId/memberships')
$message = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-inner-base.tpl');
$message
- ->setParam('{{body}}', $body, escape: false)
+ ->setParam('{{body}}', $body, escapeHtml: false)
->setParam('{{hello}}', $locale->getText("emails.invitation.hello"))
->setParam('{{footer}}', $locale->getText("emails.invitation.footer"))
->setParam('{{thanks}}', $locale->getText("emails.invitation.thanks"))
@@ -696,7 +704,7 @@ Http::post('/v1/teams/:teamId/memberships')
);
});
-Http::get('/v1/teams/:teamId/memberships')
+App::get('/v1/teams/:teamId/memberships')
->desc('List team memberships')
->groups(['api', 'teams'])
->label('scope', 'teams.read')
@@ -799,7 +807,7 @@ Http::get('/v1/teams/:teamId/memberships')
]), Response::MODEL_MEMBERSHIP_LIST);
});
-Http::get('/v1/teams/:teamId/memberships/:membershipId')
+App::get('/v1/teams/:teamId/memberships/:membershipId')
->desc('Get team membership')
->groups(['api', 'teams'])
->label('scope', 'teams.read')
@@ -855,7 +863,7 @@ Http::get('/v1/teams/:teamId/memberships/:membershipId')
$response->dynamic($membership, Response::MODEL_MEMBERSHIP);
});
-Http::patch('/v1/teams/:teamId/memberships/:membershipId')
+App::patch('/v1/teams/:teamId/memberships/:membershipId')
->desc('Update membership')
->groups(['api', 'teams'])
->label('event', 'teams.[teamId].memberships.[membershipId].update')
@@ -871,14 +879,23 @@ Http::patch('/v1/teams/:teamId/memberships/:membershipId')
->label('sdk.response.model', Response::MODEL_MEMBERSHIP)
->param('teamId', '', new UID(), 'Team ID.')
->param('membershipId', '', new UID(), 'Membership ID.')
- ->param('roles', [], new ArrayList(new Key(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of strings. Use this param to set the user\'s roles in the team. A role can be any string. Learn more about [roles and permissions](https://appwrite.io/docs/permissions). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 32 characters long.')
+ ->param('roles', [], function (Document $project) {
+ if ($project->getId() === 'console') {
+ ;
+ $roles = array_keys(Config::getParam('roles', []));
+ array_filter($roles, function ($role) {
+ return !in_array($role, [Auth::USER_ROLE_APPS, Auth::USER_ROLE_GUESTS, Auth::USER_ROLE_USERS]);
+ });
+ return new ArrayList(new WhiteList($roles), APP_LIMIT_ARRAY_PARAMS_SIZE);
+ }
+ return new ArrayList(new Key(), APP_LIMIT_ARRAY_PARAMS_SIZE);
+ }, 'An array of strings. Use this param to set the user\'s roles in the team. A role can be any string. Learn more about [roles and permissions](https://appwrite.io/docs/permissions). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 32 characters long.', false, ['project'])
->inject('request')
->inject('response')
->inject('user')
->inject('dbForProject')
->inject('queueForEvents')
- ->inject('authorization')
- ->action(function (string $teamId, string $membershipId, array $roles, Request $request, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Authorization $authorization) {
+ ->action(function (string $teamId, string $membershipId, array $roles, Request $request, Response $response, Document $user, Database $dbForProject, Event $queueForEvents) {
$team = $dbForProject->getDocument('teams', $teamId);
if ($team->isEmpty()) {
@@ -895,9 +912,9 @@ Http::patch('/v1/teams/:teamId/memberships/:membershipId')
throw new Exception(Exception::USER_NOT_FOUND);
}
- $isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles());
- $isAppUser = Auth::isAppUser($authorization->getRoles());
- $isOwner = $authorization->isRole('team:' . $team->getId() . '/owner');
+ $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
+ $isAppUser = Auth::isAppUser(Authorization::getRoles());
+ $isOwner = Authorization::isRole('team:' . $team->getId() . '/owner');
if (!$isOwner && !$isPrivilegedUser && !$isAppUser) { // Not owner, not admin, not app (server)
throw new Exception(Exception::USER_UNAUTHORIZED, 'User is not allowed to modify roles');
@@ -928,7 +945,7 @@ Http::patch('/v1/teams/:teamId/memberships/:membershipId')
);
});
-Http::patch('/v1/teams/:teamId/memberships/:membershipId/status')
+App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
->desc('Update team membership status')
->groups(['api', 'teams'])
->label('event', 'teams.[teamId].memberships.[membershipId].update.status')
@@ -954,9 +971,7 @@ Http::patch('/v1/teams/:teamId/memberships/:membershipId/status')
->inject('project')
->inject('geodb')
->inject('queueForEvents')
- ->inject('authorization')
- ->inject('authentication')
- ->action(function (string $teamId, string $membershipId, string $userId, string $secret, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Reader $geodb, Event $queueForEvents, Authorization $authorization, Authentication $authentication) {
+ ->action(function (string $teamId, string $membershipId, string $userId, string $secret, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Reader $geodb, Event $queueForEvents) {
$protocol = $request->getProtocol();
$membership = $dbForProject->getDocument('memberships', $membershipId);
@@ -965,7 +980,7 @@ Http::patch('/v1/teams/:teamId/memberships/:membershipId/status')
throw new Exception(Exception::MEMBERSHIP_NOT_FOUND);
}
- $team = $authorization->skip(fn () => $dbForProject->getDocument('teams', $teamId));
+ $team = Authorization::skip(fn () => $dbForProject->getDocument('teams', $teamId));
if ($team->isEmpty()) {
throw new Exception(Exception::TEAM_NOT_FOUND);
@@ -1000,11 +1015,11 @@ Http::patch('/v1/teams/:teamId/memberships/:membershipId/status')
->setAttribute('confirm', true)
;
- $authorization->skip(fn () => $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('emailVerification', true)));
+ Authorization::skip(fn () => $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('emailVerification', true)));
// Log user in
- $authorization->addRole(Role::user($user->getId())->toString());
+ Authorization::setRole(Role::user($user->getId())->toString());
$detector = new Detector($request->getUserAgent('UNKNOWN'));
$record = $geodb->get($request->getIP());
@@ -1034,13 +1049,13 @@ Http::patch('/v1/teams/:teamId/memberships/:membershipId/status')
$dbForProject->purgeCachedDocument('users', $user->getId());
- $authorization->addRole(Role::user($userId)->toString());
+ Authorization::setRole(Role::user($userId)->toString());
$membership = $dbForProject->updateDocument('memberships', $membership->getId(), $membership);
$dbForProject->purgeCachedDocument('users', $user->getId());
- $authorization->skip(fn () => $dbForProject->increaseDocumentAttribute('teams', $team->getId(), 'total', 1));
+ Authorization::skip(fn () => $dbForProject->increaseDocumentAttribute('teams', $team->getId(), 'total', 1));
$queueForEvents
->setParam('userId', $user->getId())
@@ -1050,13 +1065,13 @@ Http::patch('/v1/teams/:teamId/memberships/:membershipId/status')
if (!Config::getParam('domainVerification')) {
$response
- ->addHeader('X-Fallback-Cookies', \json_encode([$authentication->getCookieName() => Auth::encodeSession($user->getId(), $secret)]))
+ ->addHeader('X-Fallback-Cookies', \json_encode([Auth::$cookieName => Auth::encodeSession($user->getId(), $secret)]))
;
}
$response
- ->addCookie($authentication->getCookieName() . '_legacy', Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null)
- ->addCookie($authentication->getCookieName(), Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite'))
+ ->addCookie(Auth::$cookieName . '_legacy', Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null)
+ ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite'))
;
$response->dynamic(
@@ -1068,7 +1083,7 @@ Http::patch('/v1/teams/:teamId/memberships/:membershipId/status')
);
});
-Http::delete('/v1/teams/:teamId/memberships/:membershipId')
+App::delete('/v1/teams/:teamId/memberships/:membershipId')
->desc('Delete team membership')
->groups(['api', 'teams'])
->label('event', 'teams.[teamId].memberships.[membershipId].delete')
@@ -1086,8 +1101,7 @@ Http::delete('/v1/teams/:teamId/memberships/:membershipId')
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
- ->inject('authorization')
- ->action(function (string $teamId, string $membershipId, Response $response, Database $dbForProject, Event $queueForEvents, Authorization $authorization) {
+ ->action(function (string $teamId, string $membershipId, Response $response, Database $dbForProject, Event $queueForEvents) {
$membership = $dbForProject->getDocument('memberships', $membershipId);
@@ -1122,7 +1136,7 @@ Http::delete('/v1/teams/:teamId/memberships/:membershipId')
$dbForProject->purgeCachedDocument('users', $user->getId());
if ($membership->getAttribute('confirm')) { // Count only confirmed members
- $authorization->skip(fn () => $dbForProject->decreaseDocumentAttribute('teams', $team->getId(), 'total', 1, 0));
+ Authorization::skip(fn () => $dbForProject->decreaseDocumentAttribute('teams', $team->getId(), 'total', 1, 0));
}
$queueForEvents
@@ -1135,7 +1149,7 @@ Http::delete('/v1/teams/:teamId/memberships/:membershipId')
$response->noContent();
});
-Http::get('/v1/teams/:teamId/logs')
+App::get('/v1/teams/:teamId/logs')
->desc('List team logs')
->groups(['api', 'teams'])
->label('scope', 'teams.read')
@@ -1152,8 +1166,7 @@ Http::get('/v1/teams/:teamId/logs')
->inject('dbForProject')
->inject('locale')
->inject('geodb')
- ->inject('authorization')
- ->action(function (string $teamId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb, Authorization $authorization) {
+ ->action(function (string $teamId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) {
$team = $dbForProject->getDocument('teams', $teamId);
diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php
index e9b2bb5024..16dc395bbe 100644
--- a/app/controllers/api/users.php
+++ b/app/controllers/api/users.php
@@ -22,6 +22,7 @@ use Appwrite\Utopia\Database\Validator\Queries\Users;
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response;
use MaxMind\Db\Reader;
+use Utopia\App;
use Utopia\Audit\Audit;
use Utopia\Config\Config;
use Utopia\Database\Database;
@@ -38,16 +39,15 @@ use Utopia\Database\Validator\Queries;
use Utopia\Database\Validator\Query\Limit;
use Utopia\Database\Validator\Query\Offset;
use Utopia\Database\Validator\UID;
-use Utopia\Http\Http;
-use Utopia\Http\Validator\ArrayList;
-use Utopia\Http\Validator\Assoc;
-use Utopia\Http\Validator\Boolean;
-use Utopia\Http\Validator\Integer;
-use Utopia\Http\Validator\Range;
-use Utopia\Http\Validator\Text;
-use Utopia\Http\Validator\WhiteList;
use Utopia\Locale\Locale;
use Utopia\System\System;
+use Utopia\Validator\ArrayList;
+use Utopia\Validator\Assoc;
+use Utopia\Validator\Boolean;
+use Utopia\Validator\Integer;
+use Utopia\Validator\Range;
+use Utopia\Validator\Text;
+use Utopia\Validator\WhiteList;
/** TODO: Remove function when we move to using utopia/platform */
function createUser(string $hash, mixed $hashOptions, string $userId, ?string $email, ?string $password, ?string $phone, string $name, Document $project, Database $dbForProject, Event $queueForEvents, Hooks $hooks): Document
@@ -180,7 +180,7 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e
return $user;
}
-Http::post('/v1/users')
+App::post('/v1/users')
->desc('Create user')
->groups(['api', 'users'])
->label('event', 'users.[userId].create')
@@ -211,7 +211,7 @@ Http::post('/v1/users')
->dynamic($user, Response::MODEL_USER);
});
-Http::post('/v1/users/bcrypt')
+App::post('/v1/users/bcrypt')
->desc('Create user with bcrypt password')
->groups(['api', 'users'])
->label('event', 'users.[userId].create')
@@ -242,7 +242,7 @@ Http::post('/v1/users/bcrypt')
->dynamic($user, Response::MODEL_USER);
});
-Http::post('/v1/users/md5')
+App::post('/v1/users/md5')
->desc('Create user with MD5 password')
->groups(['api', 'users'])
->label('event', 'users.[userId].create')
@@ -273,7 +273,7 @@ Http::post('/v1/users/md5')
->dynamic($user, Response::MODEL_USER);
});
-Http::post('/v1/users/argon2')
+App::post('/v1/users/argon2')
->desc('Create user with Argon2 password')
->groups(['api', 'users'])
->label('event', 'users.[userId].create')
@@ -304,7 +304,7 @@ Http::post('/v1/users/argon2')
->dynamic($user, Response::MODEL_USER);
});
-Http::post('/v1/users/sha')
+App::post('/v1/users/sha')
->desc('Create user with SHA password')
->groups(['api', 'users'])
->label('event', 'users.[userId].create')
@@ -342,7 +342,7 @@ Http::post('/v1/users/sha')
->dynamic($user, Response::MODEL_USER);
});
-Http::post('/v1/users/phpass')
+App::post('/v1/users/phpass')
->desc('Create user with PHPass password')
->groups(['api', 'users'])
->label('event', 'users.[userId].create')
@@ -373,7 +373,7 @@ Http::post('/v1/users/phpass')
->dynamic($user, Response::MODEL_USER);
});
-Http::post('/v1/users/scrypt')
+App::post('/v1/users/scrypt')
->desc('Create user with Scrypt password')
->groups(['api', 'users'])
->label('event', 'users.[userId].create')
@@ -417,7 +417,7 @@ Http::post('/v1/users/scrypt')
->dynamic($user, Response::MODEL_USER);
});
-Http::post('/v1/users/scrypt-modified')
+App::post('/v1/users/scrypt-modified')
->desc('Create user with Scrypt modified password')
->groups(['api', 'users'])
->label('event', 'users.[userId].create')
@@ -451,8 +451,8 @@ Http::post('/v1/users/scrypt-modified')
->dynamic($user, Response::MODEL_USER);
});
-Http::post('/v1/users/:userId/targets')
- ->desc('Create User Target')
+App::post('/v1/users/:userId/targets')
+ ->desc('Create user target')
->groups(['api', 'users'])
->label('audits.event', 'target.create')
->label('audits.resource', 'target/response.$id')
@@ -540,7 +540,7 @@ Http::post('/v1/users/:userId/targets')
->dynamic($target, Response::MODEL_TARGET);
});
-Http::get('/v1/users')
+App::get('/v1/users')
->desc('List users')
->groups(['api', 'users'])
->label('scope', 'users.read')
@@ -594,7 +594,7 @@ Http::get('/v1/users')
]), Response::MODEL_USER_LIST);
});
-Http::get('/v1/users/:userId')
+App::get('/v1/users/:userId')
->desc('Get user')
->groups(['api', 'users'])
->label('scope', 'users.read')
@@ -619,7 +619,7 @@ Http::get('/v1/users/:userId')
$response->dynamic($user, Response::MODEL_USER);
});
-Http::get('/v1/users/:userId/prefs')
+App::get('/v1/users/:userId/prefs')
->desc('Get user preferences')
->groups(['api', 'users'])
->label('scope', 'users.read')
@@ -646,8 +646,8 @@ Http::get('/v1/users/:userId/prefs')
$response->dynamic(new Document($prefs), Response::MODEL_PREFERENCES);
});
-Http::get('/v1/users/:userId/targets/:targetId')
- ->desc('Get User Target')
+App::get('/v1/users/:userId/targets/:targetId')
+ ->desc('Get user target')
->groups(['api', 'users'])
->label('scope', 'targets.read')
->label('sdk.auth', [APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_ADMIN])
@@ -678,7 +678,7 @@ Http::get('/v1/users/:userId/targets/:targetId')
$response->dynamic($target, Response::MODEL_TARGET);
});
-Http::get('/v1/users/:userId/sessions')
+App::get('/v1/users/:userId/sessions')
->desc('List user sessions')
->groups(['api', 'users'])
->label('scope', 'users.read')
@@ -719,7 +719,7 @@ Http::get('/v1/users/:userId/sessions')
]), Response::MODEL_SESSION_LIST);
});
-Http::get('/v1/users/:userId/memberships')
+App::get('/v1/users/:userId/memberships')
->desc('List user memberships')
->groups(['api', 'users'])
->label('scope', 'users.read')
@@ -758,7 +758,7 @@ Http::get('/v1/users/:userId/memberships')
]), Response::MODEL_MEMBERSHIP_LIST);
});
-Http::get('/v1/users/:userId/logs')
+App::get('/v1/users/:userId/logs')
->desc('List user logs')
->groups(['api', 'users'])
->label('scope', 'users.read')
@@ -775,8 +775,7 @@ Http::get('/v1/users/:userId/logs')
->inject('dbForProject')
->inject('locale')
->inject('geodb')
- ->inject('authorization')
- ->action(function (string $userId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb, Authorization $authorization) {
+ ->action(function (string $userId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) {
$user = $dbForProject->getDocument('users', $userId);
@@ -848,8 +847,8 @@ Http::get('/v1/users/:userId/logs')
]), Response::MODEL_LOG_LIST);
});
-Http::get('/v1/users/:userId/targets')
- ->desc('List User Targets')
+App::get('/v1/users/:userId/targets')
+ ->desc('List user targets')
->groups(['api', 'users'])
->label('scope', 'targets.read')
->label('sdk.auth', [APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_ADMIN])
@@ -903,8 +902,8 @@ Http::get('/v1/users/:userId/targets')
]), Response::MODEL_TARGET_LIST);
});
-Http::get('/v1/users/identities')
- ->desc('List Identities')
+App::get('/v1/users/identities')
+ ->desc('List identities')
->groups(['api', 'users'])
->label('scope', 'users.read')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
@@ -957,7 +956,7 @@ Http::get('/v1/users/identities')
]), Response::MODEL_IDENTITY_LIST);
});
-Http::patch('/v1/users/:userId/status')
+App::patch('/v1/users/:userId/status')
->desc('Update user status')
->groups(['api', 'users'])
->label('event', 'users.[userId].update.status')
@@ -993,7 +992,7 @@ Http::patch('/v1/users/:userId/status')
$response->dynamic($user, Response::MODEL_USER);
});
-Http::put('/v1/users/:userId/labels')
+App::put('/v1/users/:userId/labels')
->desc('Update user labels')
->groups(['api', 'users'])
->label('event', 'users.[userId].update.labels')
@@ -1030,7 +1029,7 @@ Http::put('/v1/users/:userId/labels')
$response->dynamic($user, Response::MODEL_USER);
});
-Http::patch('/v1/users/:userId/verification/phone')
+App::patch('/v1/users/:userId/verification/phone')
->desc('Update phone verification')
->groups(['api', 'users'])
->label('event', 'users.[userId].update.verification')
@@ -1065,7 +1064,7 @@ Http::patch('/v1/users/:userId/verification/phone')
$response->dynamic($user, Response::MODEL_USER);
});
-Http::patch('/v1/users/:userId/name')
+App::patch('/v1/users/:userId/name')
->desc('Update name')
->groups(['api', 'users'])
->label('event', 'users.[userId].update.name')
@@ -1102,7 +1101,7 @@ Http::patch('/v1/users/:userId/name')
$response->dynamic($user, Response::MODEL_USER);
});
-Http::patch('/v1/users/:userId/password')
+App::patch('/v1/users/:userId/password')
->desc('Update password')
->groups(['api', 'users'])
->label('event', 'users.[userId].update.password')
@@ -1179,7 +1178,7 @@ Http::patch('/v1/users/:userId/password')
$response->dynamic($user, Response::MODEL_USER);
});
-Http::patch('/v1/users/:userId/email')
+App::patch('/v1/users/:userId/email')
->desc('Update email')
->groups(['api', 'users'])
->label('event', 'users.[userId].update.email')
@@ -1274,7 +1273,7 @@ Http::patch('/v1/users/:userId/email')
$response->dynamic($user, Response::MODEL_USER);
});
-Http::patch('/v1/users/:userId/phone')
+App::patch('/v1/users/:userId/phone')
->desc('Update phone')
->groups(['api', 'users'])
->label('event', 'users.[userId].update.phone')
@@ -1357,7 +1356,7 @@ Http::patch('/v1/users/:userId/phone')
$response->dynamic($user, Response::MODEL_USER);
});
-Http::patch('/v1/users/:userId/verification')
+App::patch('/v1/users/:userId/verification')
->desc('Update email verification')
->groups(['api', 'users'])
->label('event', 'users.[userId].update.verification')
@@ -1392,7 +1391,7 @@ Http::patch('/v1/users/:userId/verification')
$response->dynamic($user, Response::MODEL_USER);
});
-Http::patch('/v1/users/:userId/prefs')
+App::patch('/v1/users/:userId/prefs')
->desc('Update user preferences')
->groups(['api', 'users'])
->label('event', 'users.[userId].update.prefs')
@@ -1425,8 +1424,8 @@ Http::patch('/v1/users/:userId/prefs')
$response->dynamic(new Document($prefs), Response::MODEL_PREFERENCES);
});
-Http::patch('/v1/users/:userId/targets/:targetId')
- ->desc('Update User target')
+App::patch('/v1/users/:userId/targets/:targetId')
+ ->desc('Update user target')
->groups(['api', 'users'])
->label('audits.event', 'target.update')
->label('audits.resource', 'target/{response.$id}')
@@ -1519,7 +1518,7 @@ Http::patch('/v1/users/:userId/targets/:targetId')
->dynamic($target, Response::MODEL_TARGET);
});
-Http::patch('/v1/users/:userId/mfa')
+App::patch('/v1/users/:userId/mfa')
->desc('Update MFA')
->groups(['api', 'users'])
->label('event', 'users.[userId].update.mfa')
@@ -1557,8 +1556,8 @@ Http::patch('/v1/users/:userId/mfa')
$response->dynamic($user, Response::MODEL_USER);
});
-Http::get('/v1/users/:userId/mfa/factors')
- ->desc('List Factors')
+App::get('/v1/users/:userId/mfa/factors')
+ ->desc('List factors')
->groups(['api', 'users'])
->label('scope', 'users.read')
->label('usage.metric', 'users.{scope}.requests.read')
@@ -1590,8 +1589,8 @@ Http::get('/v1/users/:userId/mfa/factors')
$response->dynamic($factors, Response::MODEL_MFA_FACTORS);
});
-Http::get('/v1/users/:userId/mfa/recovery-codes')
- ->desc('Get MFA Recovery Codes')
+App::get('/v1/users/:userId/mfa/recovery-codes')
+ ->desc('Get MFA recovery codes')
->groups(['api', 'users'])
->label('scope', 'users.read')
->label('usage.metric', 'users.{scope}.requests.read')
@@ -1625,8 +1624,8 @@ Http::get('/v1/users/:userId/mfa/recovery-codes')
$response->dynamic($document, Response::MODEL_MFA_RECOVERY_CODES);
});
-Http::patch('/v1/users/:userId/mfa/recovery-codes')
- ->desc('Create MFA Recovery Codes')
+App::patch('/v1/users/:userId/mfa/recovery-codes')
+ ->desc('Create MFA recovery codes')
->groups(['api', 'users'])
->label('event', 'users.[userId].create.mfa.recovery-codes')
->label('scope', 'users.write')
@@ -1671,8 +1670,8 @@ Http::patch('/v1/users/:userId/mfa/recovery-codes')
$response->dynamic($document, Response::MODEL_MFA_RECOVERY_CODES);
});
-Http::put('/v1/users/:userId/mfa/recovery-codes')
- ->desc('Regenerate MFA Recovery Codes')
+App::put('/v1/users/:userId/mfa/recovery-codes')
+ ->desc('Regenerate MFA recovery codes')
->groups(['api', 'users'])
->label('event', 'users.[userId].update.mfa.recovery-codes')
->label('scope', 'users.write')
@@ -1716,8 +1715,8 @@ Http::put('/v1/users/:userId/mfa/recovery-codes')
$response->dynamic($document, Response::MODEL_MFA_RECOVERY_CODES);
});
-Http::delete('/v1/users/:userId/mfa/authenticators/:type')
- ->desc('Delete Authenticator')
+App::delete('/v1/users/:userId/mfa/authenticators/:type')
+ ->desc('Delete authenticator')
->groups(['api', 'users'])
->label('event', 'users.[userId].delete.mfa')
->label('scope', 'users.write')
@@ -1758,7 +1757,7 @@ Http::delete('/v1/users/:userId/mfa/authenticators/:type')
$response->noContent();
});
-Http::post('/v1/users/:userId/sessions')
+App::post('/v1/users/:userId/sessions')
->desc('Create session')
->groups(['api', 'users'])
->label('event', 'users.[userId].sessions.[sessionId].create')
@@ -1828,7 +1827,7 @@ Http::post('/v1/users/:userId/sessions')
->dynamic($session, Response::MODEL_SESSION);
});
-Http::post('/v1/users/:userId/tokens')
+App::post('/v1/users/:userId/tokens')
->desc('Create token')
->groups(['api', 'users'])
->label('event', 'users.[userId].tokens.[tokenId].create')
@@ -1885,7 +1884,7 @@ Http::post('/v1/users/:userId/tokens')
->dynamic($token, Response::MODEL_TOKEN);
});
-Http::delete('/v1/users/:userId/sessions/:sessionId')
+App::delete('/v1/users/:userId/sessions/:sessionId')
->desc('Delete user session')
->groups(['api', 'users'])
->label('event', 'users.[userId].sessions.[sessionId].delete')
@@ -1928,7 +1927,7 @@ Http::delete('/v1/users/:userId/sessions/:sessionId')
$response->noContent();
});
-Http::delete('/v1/users/:userId/sessions')
+App::delete('/v1/users/:userId/sessions')
->desc('Delete user sessions')
->groups(['api', 'users'])
->label('event', 'users.[userId].sessions.delete')
@@ -1970,7 +1969,7 @@ Http::delete('/v1/users/:userId/sessions')
$response->noContent();
});
-Http::delete('/v1/users/:userId')
+App::delete('/v1/users/:userId')
->desc('Delete user')
->groups(['api', 'users'])
->label('event', 'users.[userId].delete')
@@ -2012,7 +2011,7 @@ Http::delete('/v1/users/:userId')
$response->noContent();
});
-Http::delete('/v1/users/:userId/targets/:targetId')
+App::delete('/v1/users/:userId/targets/:targetId')
->desc('Delete user target')
->groups(['api', 'users'])
->label('audits.event', 'target.delete')
@@ -2063,7 +2062,7 @@ Http::delete('/v1/users/:userId/targets/:targetId')
$response->noContent();
});
-Http::delete('/v1/users/identities/:identityId')
+App::delete('/v1/users/identities/:identityId')
->desc('Delete identity')
->groups(['api', 'users'])
->label('event', 'users.[userId].identities.[identityId].delete')
@@ -2098,7 +2097,7 @@ Http::delete('/v1/users/identities/:identityId')
return $response->noContent();
});
-Http::post('/v1/users/:userId/jwts')
+App::post('/v1/users/:userId/jwts')
->desc('Create user JWT')
->groups(['api', 'users'])
->label('scope', 'users.write')
@@ -2148,7 +2147,7 @@ Http::post('/v1/users/:userId/jwts')
])]), Response::MODEL_JWT);
});
-Http::get('/v1/users/usage')
+App::get('/v1/users/usage')
->desc('Get users usage stats')
->groups(['api', 'users'])
->label('scope', 'users.read')
@@ -2161,8 +2160,8 @@ Http::get('/v1/users/usage')
->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), 'Date range.', true)
->inject('response')
->inject('dbForProject')
- ->inject('authorization')
- ->action(function (string $range, Response $response, Database $dbForProject, Authorization $authorization) {
+ ->inject('register')
+ ->action(function (string $range, Response $response, Database $dbForProject) {
$periods = Config::getParam('usage', []);
$stats = $usage = [];
@@ -2172,7 +2171,7 @@ Http::get('/v1/users/usage')
METRIC_SESSIONS,
];
- $authorization->skip(function () use ($dbForProject, $days, $metrics, &$stats) {
+ Authorization::skip(function () use ($dbForProject, $days, $metrics, &$stats) {
foreach ($metrics as $count => $metric) {
$result = $dbForProject->findOne('stats', [
Query::equal('metric', [$metric]),
diff --git a/app/controllers/api/vcs.php b/app/controllers/api/vcs.php
index 987d84bb7e..f3381490ec 100644
--- a/app/controllers/api/vcs.php
+++ b/app/controllers/api/vcs.php
@@ -8,6 +8,7 @@ use Appwrite\Utopia\Database\Validator\Queries\Installations;
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response;
use Appwrite\Vcs\Comment;
+use Utopia\App;
use Utopia\CLI\Console;
use Utopia\Config\Config;
use Utopia\Database\Database;
@@ -31,17 +32,17 @@ use Utopia\Detector\Adapter\Python;
use Utopia\Detector\Adapter\Ruby;
use Utopia\Detector\Adapter\Swift;
use Utopia\Detector\Detector;
-use Utopia\Http\Http;
-use Utopia\Http\Validator\Boolean;
-use Utopia\Http\Validator\Host;
-use Utopia\Http\Validator\Text;
use Utopia\System\System;
+use Utopia\Validator\Boolean;
+use Utopia\Validator\Host;
+use Utopia\Validator\Text;
use Utopia\VCS\Adapter\Git\GitHub;
use Utopia\VCS\Exception\RepositoryNotFound;
use function Swoole\Coroutine\batch;
-$createGitDeployments = function (GitHub $github, string $providerInstallationId, array $repositories, string $providerBranch, string $providerBranchUrl, string $providerRepositoryName, string $providerRepositoryUrl, string $providerRepositoryOwner, string $providerCommitHash, string $providerCommitAuthor, string $providerCommitAuthorUrl, string $providerCommitMessage, string $providerCommitUrl, string $providerPullRequestId, bool $external, Database $dbForConsole, Build $queueForBuilds, callable $getProjectDB, Request $request, Authorization $auth) {
+$createGitDeployments = function (GitHub $github, string $providerInstallationId, array $repositories, string $providerBranch, string $providerBranchUrl, string $providerRepositoryName, string $providerRepositoryUrl, string $providerRepositoryOwner, string $providerCommitHash, string $providerCommitAuthor, string $providerCommitAuthorUrl, string $providerCommitMessage, string $providerCommitUrl, string $providerPullRequestId, bool $external, Database $dbForConsole, Build $queueForBuilds, callable $getProjectDB, Request $request) {
+ $errors = [];
foreach ($repositories as $resource) {
try {
$resourceType = $resource->getAttribute('resourceType');
@@ -51,11 +52,11 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
}
$projectId = $resource->getAttribute('projectId');
- $project = $auth->skip(fn () => $dbForConsole->getDocument('projects', $projectId));
+ $project = Authorization::skip(fn () => $dbForConsole->getDocument('projects', $projectId));
$dbForProject = $getProjectDB($project);
$functionId = $resource->getAttribute('resourceId');
- $function = $auth->skip(fn () => $dbForProject->getDocument('functions', $functionId));
+ $function = Authorization::skip(fn () => $dbForProject->getDocument('functions', $functionId));
$functionInternalId = $function->getInternalId();
$deploymentId = ID::unique();
@@ -101,8 +102,8 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
$latestCommentId = '';
- if (!empty($providerPullRequestId) && $function->getAttribute('providerSilentMode', false)) {
- $latestComment = $auth->skip(fn () => $dbForConsole->findOne('vcsComments', [
+ if (!empty($providerPullRequestId) && $function->getAttribute('providerSilentMode', false) === false) {
+ $latestComment = Authorization::skip(fn () => $dbForConsole->findOne('vcsComments', [
Query::equal('providerRepositoryId', [$providerRepositoryId]),
Query::equal('providerPullRequestId', [$providerPullRequestId]),
Query::orderDesc('$createdAt'),
@@ -123,7 +124,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
if (!empty($latestCommentId)) {
$teamId = $project->getAttribute('teamId', '');
- $latestComment = $auth->skip(fn () => $dbForConsole->createDocument('vcsComments', new Document([
+ $latestComment = Authorization::skip(fn () => $dbForConsole->createDocument('vcsComments', new Document([
'$id' => ID::unique(),
'$permissions' => [
Permission::read(Role::team(ID::custom($teamId))),
@@ -144,7 +145,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
}
}
} elseif (!empty($providerBranch)) {
- $latestComments = $auth->skip(fn () => $dbForConsole->find('vcsComments', [
+ $latestComments = Authorization::skip(fn () => $dbForConsole->find('vcsComments', [
Query::equal('providerRepositoryId', [$providerRepositoryId]),
Query::equal('providerBranch', [$providerBranch]),
Query::orderDesc('$createdAt'),
@@ -261,8 +262,8 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
}
};
-Http::get('/v1/vcs/github/authorize')
- ->desc('Install GitHub App')
+App::get('/v1/vcs/github/authorize')
+ ->desc('Install GitHub app')
->groups(['api', 'vcs'])
->label('scope', 'vcs.read')
->label('sdk.namespace', 'vcs')
@@ -303,8 +304,8 @@ Http::get('/v1/vcs/github/authorize')
->redirect($url);
});
-Http::get('/v1/vcs/github/callback')
- ->desc('Capture installation and authorization from GitHub App')
+App::get('/v1/vcs/github/callback')
+ ->desc('Capture installation and authorization from GitHub app')
->groups(['api', 'vcs'])
->label('scope', 'public')
->label('error', __DIR__ . '/../../views/general/error.phtml')
@@ -463,7 +464,7 @@ Http::get('/v1/vcs/github/callback')
->redirect($redirectSuccess);
});
-Http::get('/v1/vcs/github/installations/:installationId/providerRepositories/:providerRepositoryId/contents')
+App::get('/v1/vcs/github/installations/:installationId/providerRepositories/:providerRepositoryId/contents')
->desc('Get files and directories of a VCS repository')
->groups(['api', 'vcs'])
->label('scope', 'vcs.read')
@@ -524,7 +525,7 @@ Http::get('/v1/vcs/github/installations/:installationId/providerRepositories/:pr
]), Response::MODEL_VCS_CONTENT_LIST);
});
-Http::post('/v1/vcs/github/installations/:installationId/providerRepositories/:providerRepositoryId/detection')
+App::post('/v1/vcs/github/installations/:installationId/providerRepositories/:providerRepositoryId/detection')
->desc('Detect runtime settings from source code')
->groups(['api', 'vcs'])
->label('scope', 'vcs.write')
@@ -596,8 +597,8 @@ Http::post('/v1/vcs/github/installations/:installationId/providerRepositories/:p
$response->dynamic(new Document($detection), Response::MODEL_DETECTION);
});
-Http::get('/v1/vcs/github/installations/:installationId/providerRepositories')
- ->desc('List Repositories')
+App::get('/v1/vcs/github/installations/:installationId/providerRepositories')
+ ->desc('List repositories')
->groups(['api', 'vcs'])
->label('scope', 'vcs.read')
->label('sdk.namespace', 'vcs')
@@ -691,7 +692,7 @@ Http::get('/v1/vcs/github/installations/:installationId/providerRepositories')
]), Response::MODEL_PROVIDER_REPOSITORY_LIST);
});
-Http::post('/v1/vcs/github/installations/:installationId/providerRepositories')
+App::post('/v1/vcs/github/installations/:installationId/providerRepositories')
->desc('Create repository')
->groups(['api', 'vcs'])
->label('scope', 'vcs.write')
@@ -792,7 +793,7 @@ Http::post('/v1/vcs/github/installations/:installationId/providerRepositories')
$response->dynamic(new Document($repository), Response::MODEL_PROVIDER_REPOSITORY);
});
-Http::get('/v1/vcs/github/installations/:installationId/providerRepositories/:providerRepositoryId')
+App::get('/v1/vcs/github/installations/:installationId/providerRepositories/:providerRepositoryId')
->desc('Get repository')
->groups(['api', 'vcs'])
->label('scope', 'vcs.read')
@@ -841,8 +842,8 @@ Http::get('/v1/vcs/github/installations/:installationId/providerRepositories/:pr
$response->dynamic(new Document($repository), Response::MODEL_PROVIDER_REPOSITORY);
});
-Http::get('/v1/vcs/github/installations/:installationId/providerRepositories/:providerRepositoryId/branches')
- ->desc('List Repository Branches')
+App::get('/v1/vcs/github/installations/:installationId/providerRepositories/:providerRepositoryId/branches')
+ ->desc('List repository branches')
->groups(['api', 'vcs'])
->label('scope', 'vcs.read')
->label('sdk.namespace', 'vcs')
@@ -890,8 +891,8 @@ Http::get('/v1/vcs/github/installations/:installationId/providerRepositories/:pr
]), Response::MODEL_BRANCH_LIST);
});
-Http::post('/v1/vcs/github/events')
- ->desc('Create Event')
+App::post('/v1/vcs/github/events')
+ ->desc('Create event')
->groups(['api', 'vcs'])
->label('scope', 'public')
->inject('gitHub')
@@ -900,9 +901,8 @@ Http::post('/v1/vcs/github/events')
->inject('dbForConsole')
->inject('getProjectDB')
->inject('queueForBuilds')
- ->inject('auth')
->action(
- function (GitHub $github, Request $request, Response $response, Database $dbForConsole, callable $getProjectDB, Build $queueForBuilds, Authorization $auth) use ($createGitDeployments) {
+ function (GitHub $github, Request $request, Response $response, Database $dbForConsole, callable $getProjectDB, Build $queueForBuilds) use ($createGitDeployments) {
$payload = $request->getRawPayload();
$signatureRemote = $request->getHeader('x-hub-signature-256', '');
$signatureLocal = System::getEnv('_APP_VCS_GITHUB_WEBHOOK_SECRET', '');
@@ -936,14 +936,14 @@ Http::post('/v1/vcs/github/events')
$github->initializeVariables($providerInstallationId, $privateKey, $githubAppId);
//find functionId from functions table
- $repositories = $auth->skip(fn () => $dbForConsole->find('repositories', [
+ $repositories = Authorization::skip(fn () => $dbForConsole->find('repositories', [
Query::equal('providerRepositoryId', [$providerRepositoryId]),
Query::limit(100),
]));
// create new deployment only on push and not when branch is created
if (!$providerBranchCreated) {
- $createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthor, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, '', false, $dbForConsole, $queueForBuilds, $getProjectDB, $request, $auth);
+ $createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthor, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, '', false, $dbForConsole, $queueForBuilds, $getProjectDB, $request);
}
} elseif ($event == $github::EVENT_INSTALLATION) {
if ($parsedPayload["action"] == "deleted") {
@@ -956,13 +956,13 @@ Http::post('/v1/vcs/github/events')
]);
foreach ($installations as $installation) {
- $repositories = $auth->skip(fn () => $dbForConsole->find('repositories', [
+ $repositories = Authorization::skip(fn () => $dbForConsole->find('repositories', [
Query::equal('installationInternalId', [$installation->getInternalId()]),
Query::limit(1000)
]));
foreach ($repositories as $repository) {
- $auth->skip(fn () => $dbForConsole->deleteDocument('repositories', $repository->getId()));
+ Authorization::skip(fn () => $dbForConsole->deleteDocument('repositories', $repository->getId()));
}
$dbForConsole->deleteDocument('installations', $installation->getId());
@@ -994,12 +994,12 @@ Http::post('/v1/vcs/github/events')
$providerCommitAuthor = $commitDetails["commitAuthor"] ?? '';
$providerCommitMessage = $commitDetails["commitMessage"] ?? '';
- $repositories = $auth->skip(fn () => $dbForConsole->find('repositories', [
+ $repositories = Authorization::skip(fn () => $dbForConsole->find('repositories', [
Query::equal('providerRepositoryId', [$providerRepositoryId]),
Query::orderDesc('$createdAt')
]));
- $createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthor, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, $providerPullRequestId, $external, $dbForConsole, $queueForBuilds, $getProjectDB, $request, $auth);
+ $createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthor, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, $providerPullRequestId, $external, $dbForConsole, $queueForBuilds, $getProjectDB, $request);
} elseif ($parsedPayload["action"] == "closed") {
// Allowed external contributions cleanup
@@ -1008,7 +1008,7 @@ Http::post('/v1/vcs/github/events')
$external = $parsedPayload["external"] ?? true;
if ($external) {
- $repositories = $auth->skip(fn () => $dbForConsole->find('repositories', [
+ $repositories = Authorization::skip(fn () => $dbForConsole->find('repositories', [
Query::equal('providerRepositoryId', [$providerRepositoryId]),
Query::orderDesc('$createdAt')
]));
@@ -1019,7 +1019,7 @@ Http::post('/v1/vcs/github/events')
if (\in_array($providerPullRequestId, $providerPullRequestIds)) {
$providerPullRequestIds = \array_diff($providerPullRequestIds, [$providerPullRequestId]);
$repository = $repository->setAttribute('providerPullRequestIds', $providerPullRequestIds);
- $repository = $auth->skip(fn () => $dbForConsole->updateDocument('repositories', $repository->getId(), $repository));
+ $repository = Authorization::skip(fn () => $dbForConsole->updateDocument('repositories', $repository->getId(), $repository));
}
}
}
@@ -1030,7 +1030,7 @@ Http::post('/v1/vcs/github/events')
}
);
-Http::get('/v1/vcs/installations')
+App::get('/v1/vcs/installations')
->desc('List installations')
->groups(['api', 'vcs'])
->label('scope', 'vcs.read')
@@ -1090,7 +1090,7 @@ Http::get('/v1/vcs/installations')
]), Response::MODEL_INSTALLATION_LIST);
});
-Http::get('/v1/vcs/installations/:installationId')
+App::get('/v1/vcs/installations/:installationId')
->desc('Get installation')
->groups(['api', 'vcs'])
->label('scope', 'vcs.read')
@@ -1119,8 +1119,8 @@ Http::get('/v1/vcs/installations/:installationId')
$response->dynamic($installation, Response::MODEL_INSTALLATION);
});
-Http::delete('/v1/vcs/installations/:installationId')
- ->desc('Delete Installation')
+App::delete('/v1/vcs/installations/:installationId')
+ ->desc('Delete installation')
->groups(['api', 'vcs'])
->label('scope', 'vcs.write')
->label('sdk.namespace', 'vcs')
@@ -1152,7 +1152,7 @@ Http::delete('/v1/vcs/installations/:installationId')
$response->noContent();
});
-Http::patch('/v1/vcs/github/installations/:installationId/repositories/:repositoryId')
+App::patch('/v1/vcs/github/installations/:installationId/repositories/:repositoryId')
->desc('Authorize external deployment')
->groups(['api', 'vcs'])
->label('scope', 'vcs.write')
@@ -1172,15 +1172,14 @@ Http::patch('/v1/vcs/github/installations/:installationId/repositories/:reposito
->inject('dbForConsole')
->inject('getProjectDB')
->inject('queueForBuilds')
- ->inject('auth')
- ->action(function (string $installationId, string $repositoryId, string $providerPullRequestId, GitHub $github, Request $request, Response $response, Document $project, Database $dbForConsole, callable $getProjectDB, Build $queueForBuilds, Authorization $auth) use ($createGitDeployments) {
+ ->action(function (string $installationId, string $repositoryId, string $providerPullRequestId, GitHub $github, Request $request, Response $response, Document $project, Database $dbForConsole, callable $getProjectDB, Build $queueForBuilds) use ($createGitDeployments) {
$installation = $dbForConsole->getDocument('installations', $installationId);
if ($installation->isEmpty()) {
throw new Exception(Exception::INSTALLATION_NOT_FOUND);
}
- $repository = $auth->skip(fn () => $dbForConsole->getDocument('repositories', $repositoryId, [
+ $repository = Authorization::skip(fn () => $dbForConsole->getDocument('repositories', $repositoryId, [
Query::equal('projectInternalId', [$project->getInternalId()])
]));
@@ -1197,7 +1196,7 @@ Http::patch('/v1/vcs/github/installations/:installationId/repositories/:reposito
// TODO: Delete from array when PR is closed
- $repository = $auth->skip(fn () => $dbForConsole->updateDocument('repositories', $repository->getId(), $repository));
+ $repository = Authorization::skip(fn () => $dbForConsole->updateDocument('repositories', $repository->getId(), $repository));
$privateKey = System::getEnv('_APP_VCS_GITHUB_PRIVATE_KEY');
$githubAppId = System::getEnv('_APP_VCS_GITHUB_APP_ID');
@@ -1221,7 +1220,7 @@ Http::patch('/v1/vcs/github/installations/:installationId/repositories/:reposito
$providerBranch = \explode(':', $pullRequestResponse['head']['label'])[1] ?? '';
$providerCommitHash = $pullRequestResponse['head']['sha'] ?? '';
- $createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerCommitHash, $providerPullRequestId, true, $dbForConsole, $queueForBuilds, $getProjectDB, $request, $auth);
+ $createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerCommitHash, $providerPullRequestId, true, $dbForConsole, $queueForBuilds, $getProjectDB, $request);
$response->noContent();
});
diff --git a/app/controllers/general.php b/app/controllers/general.php
index 9df5088666..20e44b6edd 100644
--- a/app/controllers/general.php
+++ b/app/controllers/general.php
@@ -1,5 +1,7 @@
label('error', __DIR__ . '/../views/general/error.phtml');
+ $utopia->getRoute()?->label('error', __DIR__ . '/../views/general/error.phtml');
$host = $request->getHostname() ?? '';
- $rule = $auth->skip(
+ $route = Authorization::skip(
fn () => $dbForConsole->find('rules', [
Query::equal('domain', [$host]),
Query::limit(1)
])
)[0] ?? null;
- if ($rule === null) {
+ if ($route === null) {
if ($host === System::getEnv('_APP_DOMAIN_FUNCTIONS', '')) {
throw new AppwriteException(AppwriteException::GENERAL_ACCESS_FORBIDDEN, 'This domain cannot be used for security reasons. Please use any subdomain instead.');
}
@@ -72,12 +73,12 @@ function router(Database $dbForConsole, callable $getProjectDB, Request $request
}
// Act as API - no Proxy logic
- $route?->label('error', '');
+ $utopia->getRoute()?->label('error', '');
return false;
}
- $projectId = $rule->getAttribute('projectId');
- $project = $auth->skip(
+ $projectId = $route->getAttribute('projectId');
+ $project = Authorization::skip(
fn () => $dbForConsole->getDocument('projects', $projectId)
);
if (array_key_exists('proxy', $project->getAttribute('services', []))) {
@@ -88,16 +89,16 @@ function router(Database $dbForConsole, callable $getProjectDB, Request $request
}
// Skip Appwrite Router for ACME challenge. Nessessary for certificate generation
- $path = ($request->getURI() ?? '/');
+ $path = ($swooleRequest->server['request_uri'] ?? '/');
if (\str_starts_with($path, '/.well-known/acme-challenge')) {
return false;
}
- $type = $rule->getAttribute('resourceType');
+ $type = $route->getAttribute('resourceType');
if ($type === 'function') {
- $route->label('sdk.namespace', 'functions');
- $route->label('sdk.method', 'createExecution');
+ $utopia->getRoute()?->label('sdk.namespace', 'functions');
+ $utopia->getRoute()?->label('sdk.method', 'createExecution');
if (System::getEnv('_APP_OPTIONS_FUNCTIONS_FORCE_HTTPS', 'disabled') === 'enabled') { // Force HTTPS
if ($request->getProtocol() !== 'https') {
@@ -109,25 +110,26 @@ function router(Database $dbForConsole, callable $getProjectDB, Request $request
}
}
- $functionId = $rule->getAttribute('resourceId');
- $projectId = $rule->getAttribute('projectId');
+ $functionId = $route->getAttribute('resourceId');
+ $projectId = $route->getAttribute('projectId');
- $path = ($request->getURI() ?? '/');
- $query = ($request->getQueryString() ?? '');
+ $path = ($swooleRequest->server['request_uri'] ?? '/');
+ $query = ($swooleRequest->server['query_string'] ?? '');
if (!empty($query)) {
$path .= '?' . $query;
}
- $body = $request->getRawPayload() ?? '';
- $method = $request->getMethod();
+
+ $body = $swooleRequest->getContent() ?? '';
+ $method = $swooleRequest->server['request_method'];
$requestHeaders = $request->getHeaders();
- $project = $auth->skip(fn () => $dbForConsole->getDocument('projects', $projectId));
+ $project = Authorization::skip(fn () => $dbForConsole->getDocument('projects', $projectId));
$dbForProject = $getProjectDB($project);
- $function = $auth->skip(fn () => $dbForProject->getDocument('functions', $functionId));
+ $function = Authorization::skip(fn () => $dbForProject->getDocument('functions', $functionId));
if ($function->isEmpty() || !$function->getAttribute('enabled')) {
throw new AppwriteException(AppwriteException::FUNCTION_NOT_FOUND);
@@ -143,7 +145,7 @@ function router(Database $dbForConsole, callable $getProjectDB, Request $request
throw new AppwriteException(AppwriteException::FUNCTION_RUNTIME_UNSUPPORTED, 'Runtime "' . $function->getAttribute('runtime', '') . '" is not supported');
}
- $deployment = $auth->skip(fn () => $dbForProject->getDocument('deployments', $function->getAttribute('deployment', '')));
+ $deployment = Authorization::skip(fn () => $dbForProject->getDocument('deployments', $function->getAttribute('deployment', '')));
if ($deployment->getAttribute('resourceId') !== $function->getId()) {
throw new AppwriteException(AppwriteException::DEPLOYMENT_NOT_FOUND, 'Deployment not found. Create a deployment before trying to execute a function');
@@ -154,7 +156,7 @@ function router(Database $dbForConsole, callable $getProjectDB, Request $request
}
/** Check if build has completed */
- $build = $auth->skip(fn () => $dbForProject->getDocument('builds', $deployment->getAttribute('buildId', '')));
+ $build = Authorization::skip(fn () => $dbForProject->getDocument('builds', $deployment->getAttribute('buildId', '')));
if ($build->isEmpty()) {
throw new AppwriteException(AppwriteException::BUILD_NOT_FOUND);
}
@@ -215,7 +217,7 @@ function router(Database $dbForConsole, callable $getProjectDB, Request $request
'deploymentInternalId' => $deployment->getInternalId(),
'deploymentId' => $deployment->getId(),
'trigger' => 'http', // http / schedule / event
- 'status' => 'processing', // waiting / processing / completed / failed
+ 'status' => 'processing', // waiting / processing / completed / failed
'responseStatusCode' => 0,
'responseHeaders' => [],
'requestPath' => $path,
@@ -311,6 +313,7 @@ function router(Database $dbForConsole, callable $getProjectDB, Request $request
cpus: $spec['cpus'] ?? APP_FUNCTION_CPUS_DEFAULT,
memory: $spec['memory'] ?? APP_FUNCTION_MEMORY_DEFAULT,
logging: $function->getAttribute('logging', true),
+ requestTimeout: 30
);
$headersFiltered = [];
@@ -328,6 +331,7 @@ function router(Database $dbForConsole, callable $getProjectDB, Request $request
$execution->setAttribute('logs', $executionResponse['logs']);
$execution->setAttribute('errors', $executionResponse['errors']);
$execution->setAttribute('duration', $executionResponse['duration']);
+
} catch (\Throwable $th) {
$durationEnd = \microtime(true);
@@ -362,8 +366,7 @@ function router(Database $dbForConsole, callable $getProjectDB, Request $request
->trigger()
;
- /** @var Document $execution */
- $execution = $auth->skip(fn () => $dbForProject->createDocument('executions', $execution));
+ $execution = Authorization::skip(fn () => $dbForProject->createDocument('executions', $execution));
}
$execution->setAttribute('logs', '');
@@ -395,15 +398,18 @@ function router(Database $dbForConsole, callable $getProjectDB, Request $request
return true;
} elseif ($type === 'api') {
- $route?->label('error', '');
+ $utopia->getRoute()?->label('error', '');
return false;
} else {
throw new AppwriteException(AppwriteException::GENERAL_SERVER_ERROR, 'Unknown resource type ' . $type);
}
+
+ $utopia->getRoute()?->label('error', '');
+ return false;
}
/*
-Http::init()
+App::init()
->groups(['api'])
->inject('project')
->inject('mode')
@@ -414,7 +420,7 @@ Http::init()
});
*/
-Http::init()
+App::init()
->groups(['database', 'functions', 'storage', 'messaging'])
->inject('project')
->inject('request')
@@ -427,11 +433,12 @@ Http::init()
}
});
-Http::init()
+App::init()
->groups(['api', 'web'])
+ ->inject('utopia')
+ ->inject('swooleRequest')
->inject('request')
->inject('response')
- ->inject('route')
->inject('console')
->inject('project')
->inject('dbForConsole')
@@ -443,27 +450,7 @@ Http::init()
->inject('queueForUsage')
->inject('queueForEvents')
->inject('queueForCertificates')
- ->inject('authorization')
- ->action(function (
- Request $request,
- Response $response,
- Route $route,
- Document $console,
- Document $project,
- Database $dbForConsole,
- $getProjectDB,
- Locale $locale,
- array $localeCodes,
- array $clients,
- /**
- * @disregard P1009 Undefined type
- */
- Reader $geodb,
- Usage $queueForUsage,
- Event $queueForEvents,
- Certificate $queueForCertificates,
- Authorization $authorization
- ) {
+ ->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Document $console, Document $project, Database $dbForConsole, callable $getProjectDB, Locale $locale, array $localeCodes, array $clients, Reader $geodb, Usage $queueForUsage, Event $queueForEvents, Certificate $queueForCertificates) {
/*
* Appwrite Router
*/
@@ -471,7 +458,7 @@ Http::init()
$mainDomain = System::getEnv('_APP_DOMAIN', '');
// Only run Router when external domain
if ($host !== $mainDomain) {
- if (router($dbForConsole, $getProjectDB, $request, $response, $route, $queueForEvents, $queueForUsage, $geodb, $authorization)) {
+ if (router($utopia, $dbForConsole, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForUsage, $geodb)) {
return;
}
}
@@ -479,8 +466,8 @@ Http::init()
/*
* Request format
*/
- //$route = $utopia->getRoute();
- //Request::setRoute($route);
+ $route = $utopia->getRoute();
+ Request::setRoute($route);
if ($route === null) {
return $response
@@ -512,7 +499,7 @@ Http::init()
} elseif (str_starts_with($request->getURI(), '/.well-known/acme-challenge')) {
Console::warning('Skipping SSL certificates generation on ACME challenge.');
} else {
- $authorization->disable();
+ Authorization::disable();
$envDomain = System::getEnv('_APP_DOMAIN', '');
$mainDomain = null;
@@ -551,12 +538,12 @@ Http::init()
}
$domains[$domain->get()] = true;
- $authorization->reset(); // ensure authorization is re-enabled
+ Authorization::reset(); // ensure authorization is re-enabled
}
Config::setParam('domains', $domains);
}
- $localeParam = (string)$request->getParam('locale', $request->getHeader('x-appwrite-locale', ''));
+ $localeParam = (string) $request->getParam('locale', $request->getHeader('x-appwrite-locale', ''));
if (\in_array($localeParam, $localeCodes)) {
$locale->setDefault($localeParam);
}
@@ -584,7 +571,7 @@ Http::init()
Config::setParam(
'domainVerification',
($selfDomain->getRegisterable() === $endDomain->getRegisterable()) &&
- $endDomain->getRegisterable() !== ''
+ $endDomain->getRegisterable() !== ''
);
$isLocalHost = $request->getHostname() === 'localhost' || $request->getHostname() === 'localhost:' . $request->getPort();
@@ -599,8 +586,8 @@ Http::init()
? null
: (
$isConsoleProject && $isConsoleRootSession
- ? '.' . $selfDomain->getRegisterable()
- : '.' . $request->getHostname()
+ ? '.' . $selfDomain->getRegisterable()
+ : '.' . $request->getHostname()
)
);
@@ -619,7 +606,7 @@ Http::init()
$response->addFilter(new ResponseV18());
}
if (version_compare($responseFormat, APP_VERSION_STABLE, '>')) {
- $response->addHeader('X-Appwrite-Warning', "The current SDK is built for Appwrite " . $responseFormat . ". However, the current Appwrite server version is " . APP_VERSION_STABLE . ". Please downgrade your SDK to match the Appwrite version: https://appwrite.io/docs/sdks");
+ $response->addHeader('X-Appwrite-Warning', "The current SDK is built for Appwrite " . $responseFormat . ". However, the current Appwrite server version is ". APP_VERSION_STABLE . ". Please downgrade your SDK to match the Appwrite version: https://appwrite.io/docs/sdks");
}
}
@@ -630,9 +617,7 @@ Http::init()
* @see https://www.owasp.org/index.php/List_of_useful_HTTP_headers
*/
if (System::getEnv('_APP_OPTIONS_FORCE_HTTPS', 'disabled') === 'enabled') { // Force HTTPS
- if ($request->getProtocol() !== 'https' // localhost allowed for proxy, APP_HOSTNAME_INTERNAL allowed for migrations
- && ($request->getHeader('host') ?? '') !== 'localhost'
- && ($request->getHeader('host') ?? '') !== APP_HOSTNAME_INTERNAL) {
+ if ($request->getProtocol() !== 'https' && ($swooleRequest->header['host'] ?? '') !== 'localhost' && ($swooleRequest->header['host'] ?? '') !== APP_HOSTNAME_INTERNAL) { // localhost allowed for proxy, APP_HOSTNAME_INTERNAL allowed for migrations
if ($request->getMethod() !== Request::METHOD_GET) {
throw new AppwriteException(AppwriteException::GENERAL_PROTOCOL_UNSUPPORTED, 'Method unsupported over HTTP. Please use HTTPS instead.');
}
@@ -672,8 +657,9 @@ Http::init()
}
});
-Http::options()
- ->inject('route')
+App::options()
+ ->inject('utopia')
+ ->inject('swooleRequest')
->inject('request')
->inject('response')
->inject('dbForConsole')
@@ -681,8 +667,7 @@ Http::options()
->inject('queueForEvents')
->inject('queueForUsage')
->inject('geodb')
- ->inject('authorization')
- ->action(function (Route $route, Request $request, Response $response, Database $dbForConsole, callable $getProjectDB, Event $queueForEvents, Usage $queueForUsage, Reader $geodb, Authorization $authorization) {
+ ->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Database $dbForConsole, callable $getProjectDB, Event $queueForEvents, Usage $queueForUsage, Reader $geodb) {
/*
* Appwrite Router
*/
@@ -690,7 +675,7 @@ Http::options()
$mainDomain = System::getEnv('_APP_DOMAIN', '');
// Only run Router when external domain
if ($host !== $mainDomain) {
- if (router($dbForConsole, $getProjectDB, $request, $response, $route, $queueForEvents, $queueForUsage, $geodb, $authorization)) {
+ if (router($utopia, $dbForConsole, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForUsage, $geodb)) {
return;
}
}
@@ -707,25 +692,18 @@ Http::options()
->noContent();
});
-Http::error()
+App::error()
->inject('error')
- ->inject('user')
- ->inject('route')
+ ->inject('utopia')
->inject('request')
->inject('response')
->inject('project')
->inject('logger')
->inject('log')
- ->inject('authorization')
- ->inject('connections')
->inject('queueForUsage')
- ->action(function (Throwable $error, Document $user, ?Route $route, Request $request, Response $response, Document $project, ?Logger $logger, Log $log, Authorization $authorization, Connections $connections, Usage $queueForUsage) {
+ ->action(function (Throwable $error, App $utopia, Request $request, Response $response, Document $project, ?Logger $logger, Log $log, Usage $queueForUsage) {
$version = System::getEnv('_APP_VERSION', 'UNKNOWN');
-
- if (is_null($route)) {
- $route = new Route($request->getMethod(), $request->getURI());
- }
-
+ $route = $utopia->getRoute();
$class = \get_class($error);
$code = $error->getCode();
$message = $error->getMessage();
@@ -746,9 +724,9 @@ Http::error()
Console::error('[Error] File: ' . $file);
Console::error('[Error] Line: ' . $line);
}
+
switch ($class) {
- case 'Utopia\Servers\Exception':
- case 'Utopia\Http\Exception':
+ case 'Utopia\Exception':
$error = new AppwriteException(AppwriteException::GENERAL_UNKNOWN, $message, $code, $error);
switch ($code) {
case 400:
@@ -793,36 +771,35 @@ Http::error()
} else {
$publish = $error->getCode() === 0 || $error->getCode() >= 500;
}
+
if ($error->getCode() >= 400 && $error->getCode() < 500) {
// Register error logger
$providerName = System::getEnv('_APP_EXPERIMENT_LOGGING_PROVIDER', '');
$providerConfig = System::getEnv('_APP_EXPERIMENT_LOGGING_CONFIG', '');
- if (!(empty($providerName) || empty($providerConfig))) {
- try {
- $loggingProvider = new DSN($providerConfig);
- $providerName = $loggingProvider->getScheme();
+ try {
+ $loggingProvider = new DSN($providerConfig ?? '');
+ $providerName = $loggingProvider->getScheme();
- if (!empty($providerName) && $providerName === 'sentry') {
- $key = $loggingProvider->getPassword();
- $projectId = $loggingProvider->getUser() ?? '';
- $host = 'https://' . $loggingProvider->getHost();
+ if (!empty($providerName) && $providerName === 'sentry') {
+ $key = $loggingProvider->getPassword();
+ $projectId = $loggingProvider->getUser() ?? '';
+ $host = 'https://' . $loggingProvider->getHost();
- $adapter = new Sentry($projectId, $key, $host);
- $logger = new Logger($adapter);
- $logger->setSample(0.04);
- $publish = true;
- } else {
- throw new \Exception('Invalid experimental logging provider');
- }
- } catch (\Throwable $th) {
- Console::warning('Failed to initialize logging provider: ' . $th->getMessage());
+ $adapter = new Sentry($projectId, $key, $host);
+ $logger = new Logger($adapter);
+ $logger->setSample(0.04);
+ $publish = true;
+ } else {
+ throw new \Exception('Invalid experimental logging provider');
}
+ } catch (\Throwable $th) {
+ Console::warning('Failed to initialize logging provider: ' . $th->getMessage());
}
}
if ($publish && $project->getId() !== 'console') {
- if (!Auth::isPrivilegedUser($authorization->getRoles())) {
+ if (!Auth::isPrivilegedUser(Authorization::getRoles())) {
$fileSize = 0;
$file = $request->getFiles('file');
if (!empty($file)) {
@@ -841,7 +818,14 @@ Http::error()
}
- if ($logger && ($publish || $error->getCode() === 0)) {
+ if ($logger && $publish) {
+ try {
+ /** @var Utopia\Database\Document $user */
+ $user = $utopia->getResource('user');
+ } catch (\Throwable) {
+ // All good, user is optional information for logger
+ }
+
if (isset($user) && !$user->isEmpty()) {
$log->setUser(new User($user->getId()));
}
@@ -871,7 +855,7 @@ Http::error()
$log->addExtra('file', $error->getFile());
$log->addExtra('line', $error->getLine());
$log->addExtra('trace', $error->getTraceAsString());
- $log->addExtra('roles', $authorization->getRoles());
+ $log->addExtra('roles', Authorization::getRoles());
$action = $route->getLabel("sdk.namespace", "UNKNOWN_NAMESPACE") . '.' . $route->getLabel("sdk.method", "UNKNOWN_METHOD");
$log->setAction($action);
@@ -879,13 +863,17 @@ Http::error()
$isProduction = System::getEnv('_APP_ENV', 'development') === 'production';
$log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING);
- $responseCode = $logger->addLog($log);
- Console::info('Log pushed with status code: ' . $responseCode);
+ try {
+ $responseCode = $logger->addLog($log);
+ Console::info('Error log pushed with status code: ' . $responseCode);
+ } catch (Throwable $th) {
+ Console::error('Error pushing log: ' . $th->getMessage());
+ }
}
/** Wrap all exceptions inside Appwrite\Extend\Exception */
if (!($error instanceof AppwriteException)) {
- $error = new AppwriteException(AppwriteException::GENERAL_UNKNOWN, $message, (int)$code, $error);
+ $error = new AppwriteException(AppwriteException::GENERAL_UNKNOWN, $message, $code, $error);
}
switch ($code) { // Don't show 500 errors!
@@ -905,14 +893,14 @@ Http::error()
break;
default:
$code = 500; // All other errors get the generic 500 server error status code
- $message = (Http::getMode() === Http::MODE_TYPE_DEVELOPMENT) ? $message : 'Server Error';
+ $message = 'Server Error';
}
//$_SERVER = []; // Reset before reporting to error log to avoid keys being compromised
$type = $error->getType();
- $output = ((Http::isDevelopment())) ? [
+ $output = ((App::isDevelopment())) ? [
'message' => $message,
'code' => $code,
'file' => $file,
@@ -940,7 +928,7 @@ Http::error()
$layout
->setParam('title', $project->getAttribute('name') . ' - Error')
- ->setParam('development', Http::isDevelopment())
+ ->setParam('development', App::isDevelopment())
->setParam('projectName', $project->getAttribute('name'))
->setParam('projectURL', $project->getAttribute('url'))
->setParam('message', $output['message'] ?? '')
@@ -951,18 +939,18 @@ Http::error()
$response->html($layout->render());
}
- $connections->reclaim();
-
$response->dynamic(
new Document($output),
- Http::isDevelopment() ? Response::MODEL_ERROR_DEV : Response::MODEL_ERROR
+ $utopia->isDevelopment() ? Response::MODEL_ERROR_DEV : Response::MODEL_ERROR
);
});
-Http::get('/robots.txt')
+App::get('/robots.txt')
->desc('Robots.txt File')
->label('scope', 'public')
->label('docs', false)
+ ->inject('utopia')
+ ->inject('swooleRequest')
->inject('request')
->inject('response')
->inject('dbForConsole')
@@ -970,9 +958,7 @@ Http::get('/robots.txt')
->inject('queueForEvents')
->inject('queueForUsage')
->inject('geodb')
- ->inject('route')
- ->inject('authorization')
- ->action(function (Request $request, Response $response, Database $dbForConsole, callable $getProjectDB, Event $queueForEvents, Usage $queueForUsage, Reader $geodb, ?Route $route, Authorization $authorization) {
+ ->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Database $dbForConsole, callable $getProjectDB, Event $queueForEvents, Usage $queueForUsage, Reader $geodb) {
$host = $request->getHostname() ?? '';
$mainDomain = System::getEnv('_APP_DOMAIN', '');
@@ -980,17 +966,16 @@ Http::get('/robots.txt')
$template = new View(__DIR__ . '/../views/general/robots.phtml');
$response->text($template->render(false));
} else {
- if (is_null($route)) {
- $route = new Route($request->getMethod(), $request->getURI());
- }
- router($dbForConsole, $getProjectDB, $request, $response, $route, $queueForEvents, $queueForUsage, $geodb, $authorization);
+ router($utopia, $dbForConsole, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForUsage, $geodb);
}
});
-Http::get('/humans.txt')
+App::get('/humans.txt')
->desc('Humans.txt File')
->label('scope', 'public')
->label('docs', false)
+ ->inject('utopia')
+ ->inject('swooleRequest')
->inject('request')
->inject('response')
->inject('dbForConsole')
@@ -998,9 +983,7 @@ Http::get('/humans.txt')
->inject('queueForEvents')
->inject('queueForUsage')
->inject('geodb')
- ->inject('route')
- ->inject('authorization')
- ->action(function (Request $request, Response $response, Database $dbForConsole, callable $getProjectDB, Event $queueForEvents, Usage $queueForUsage, Reader $geodb, Route $route, Authorization $authorization) {
+ ->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Database $dbForConsole, callable $getProjectDB, Event $queueForEvents, Usage $queueForUsage, Reader $geodb) {
$host = $request->getHostname() ?? '';
$mainDomain = System::getEnv('_APP_DOMAIN', '');
@@ -1008,11 +991,11 @@ Http::get('/humans.txt')
$template = new View(__DIR__ . '/../views/general/humans.phtml');
$response->text($template->render(false));
} else {
- router($dbForConsole, $getProjectDB, $request, $response, $route, $queueForEvents, $queueForUsage, $geodb, $authorization);
+ router($utopia, $dbForConsole, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForUsage, $geodb);
}
});
-Http::get('/.well-known/acme-challenge/*')
+App::get('/.well-known/acme-challenge/*')
->desc('SSL Verification')
->label('scope', 'public')
->label('docs', false)
@@ -1062,32 +1045,16 @@ Http::get('/.well-known/acme-challenge/*')
$response->text($content);
});
-Http::wildcard()
+include_once __DIR__ . '/shared/api.php';
+include_once __DIR__ . '/shared/api/auth.php';
+
+App::wildcard()
->groups(['api'])
->label('scope', 'global')
->action(function () {
throw new AppwriteException(AppwriteException::GENERAL_ROUTE_NOT_FOUND);
});
-include_once 'mock.php';
-include_once 'shared/api.php';
-include_once 'shared/api/auth.php';
-include_once 'api/account.php';
-include_once 'api/avatars.php';
-include_once 'api/console.php';
-include_once 'api/databases.php';
-include_once 'api/functions.php';
-include_once 'api/graphql.php';
-include_once 'api/health.php';
-include_once 'api/locale.php';
-include_once 'api/messaging.php';
-include_once 'api/migrations.php';
-include_once 'api/project.php';
-include_once 'api/projects.php';
-include_once 'api/proxy.php';
-include_once 'api/storage.php';
-include_once 'api/teams.php';
-include_once 'api/users.php';
-include_once 'api/vcs.php';
-include_once 'web/console.php';
-include_once 'web/home.php';
+foreach (Config::getParam('services', []) as $service) {
+ include_once $service['controller'];
+}
diff --git a/app/controllers/mock.php b/app/controllers/mock.php
index 70ca5e9048..bc071fc885 100644
--- a/app/controllers/mock.php
+++ b/app/controllers/mock.php
@@ -3,7 +3,9 @@
global $utopia, $request, $response;
use Appwrite\Extend\Exception;
+use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response;
+use Utopia\App;
use Utopia\Config\Config;
use Utopia\Database\Database;
use Utopia\Database\Document;
@@ -11,15 +13,13 @@ use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Validator\UID;
-use Utopia\Http\Http;
-use Utopia\Http\Route;
-use Utopia\Http\Validator\Host;
-use Utopia\Http\Validator\Text;
-use Utopia\Http\Validator\WhiteList;
use Utopia\System\System;
+use Utopia\Validator\Host;
+use Utopia\Validator\Text;
+use Utopia\Validator\WhiteList;
use Utopia\VCS\Adapter\Git\GitHub;
-Http::get('/v1/mock/tests/general/oauth2')
+App::get('/v1/mock/tests/general/oauth2')
->desc('OAuth Login')
->groups(['mock'])
->label('scope', 'public')
@@ -35,7 +35,7 @@ Http::get('/v1/mock/tests/general/oauth2')
$response->redirect($redirectURI . '?' . \http_build_query(['code' => 'abcdef', 'state' => $state]));
});
-Http::get('/v1/mock/tests/general/oauth2/token')
+App::get('/v1/mock/tests/general/oauth2/token')
->desc('OAuth2 Token')
->groups(['mock'])
->label('scope', 'public')
@@ -81,7 +81,7 @@ Http::get('/v1/mock/tests/general/oauth2/token')
}
});
-Http::get('/v1/mock/tests/general/oauth2/user')
+App::get('/v1/mock/tests/general/oauth2/user')
->desc('OAuth2 User')
->groups(['mock'])
->label('scope', 'public')
@@ -101,7 +101,7 @@ Http::get('/v1/mock/tests/general/oauth2/user')
]);
});
-Http::get('/v1/mock/tests/general/oauth2/success')
+App::get('/v1/mock/tests/general/oauth2/success')
->desc('OAuth2 Success')
->groups(['mock'])
->label('scope', 'public')
@@ -114,7 +114,7 @@ Http::get('/v1/mock/tests/general/oauth2/success')
]);
});
-Http::get('/v1/mock/tests/general/oauth2/failure')
+App::get('/v1/mock/tests/general/oauth2/failure')
->desc('OAuth2 Failure')
->groups(['mock'])
->label('scope', 'public')
@@ -129,7 +129,7 @@ Http::get('/v1/mock/tests/general/oauth2/failure')
]);
});
-Http::patch('/v1/mock/functions-v2')
+App::patch('/v1/mock/functions-v2')
->desc('Update Function Version to V2 (outdated code syntax)')
->groups(['mock', 'api', 'functions'])
->label('scope', 'functions.write')
@@ -155,10 +155,10 @@ Http::patch('/v1/mock/functions-v2')
$response->noContent();
});
-Http::post('/v1/mock/api-key-unprefixed')
+App::post('/v1/mock/api-key-unprefixed')
->desc('Create API Key (without standard prefix)')
->groups(['mock', 'api', 'projects'])
- ->label('scope', 'projects.write')
+ ->label('scope', 'public')
->label('docs', false)
->param('projectId', '', new UID(), 'Project ID.')
->inject('response')
@@ -204,7 +204,7 @@ Http::post('/v1/mock/api-key-unprefixed')
->dynamic($key, Response::MODEL_KEY);
});
-Http::get('/v1/mock/github/callback')
+App::get('/v1/mock/github/callback')
->desc('Create installation document using GitHub installation id')
->groups(['mock', 'api', 'vcs'])
->label('scope', 'public')
@@ -264,13 +264,15 @@ Http::get('/v1/mock/github/callback')
]);
});
-Http::shutdown()
+App::shutdown()
->groups(['mock'])
- ->inject('route')
+ ->inject('utopia')
->inject('response')
- ->action(function (Route $route, Response $response) {
+ ->inject('request')
+ ->action(function (App $utopia, Response $response, Request $request) {
$result = [];
+ $route = $utopia->getRoute();
$path = APP_STORAGE_CACHE . '/tests.json';
$tests = (\file_exists($path)) ? \json_decode(\file_get_contents($path), true) : [];
diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php
index 5e97d7292a..f0d896c95a 100644
--- a/app/controllers/shared/api.php
+++ b/app/controllers/shared/api.php
@@ -15,11 +15,11 @@ use Appwrite\Event\Usage;
use Appwrite\Extend\Exception;
use Appwrite\Extend\Exception as AppwriteException;
use Appwrite\Messaging\Adapter\Realtime;
-use Appwrite\Utopia\Queue\Connections;
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response;
use Utopia\Abuse\Abuse;
use Utopia\Abuse\Adapters\Database\TimeLimit;
+use Utopia\App;
use Utopia\Cache\Adapter\Filesystem;
use Utopia\Cache\Cache;
use Utopia\Config\Config;
@@ -28,11 +28,8 @@ use Utopia\Database\DateTime;
use Utopia\Database\Document;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Validator\Authorization;
-use Utopia\Database\Validator\Authorization\Input;
-use Utopia\Http\Http;
-use Utopia\Http\Route;
-use Utopia\Http\Validator\WhiteList;
use Utopia\System\System;
+use Utopia\Validator\WhiteList;
$parseLabel = function (string $label, array $responsePayload, array $requestParams, Document $user) {
preg_match_all('/{(.*?)}/', $label, $matches);
@@ -98,7 +95,7 @@ $databaseListener = function (string $event, Document $document, Document $proje
$databaseInternalId = $parts[1] ?? 0;
$queueForUsage
->addMetric(METRIC_COLLECTIONS, $value) // per project
- ->addMetric(str_replace('{databaseInternalId}', $databaseInternalId, METRIC_DATABASE_ID_COLLECTIONS), $value) // per database
+ ->addMetric(str_replace('{databaseInternalId}', $databaseInternalId, METRIC_DATABASE_ID_COLLECTIONS), $value)
;
if ($event === Database::EVENT_DOCUMENT_DELETE) {
@@ -153,9 +150,9 @@ $databaseListener = function (string $event, Document $document, Document $proje
}
};
-Http::init()
+App::init()
->groups(['api'])
- ->inject('route')
+ ->inject('utopia')
->inject('request')
->inject('dbForConsole')
->inject('project')
@@ -163,41 +160,22 @@ Http::init()
->inject('session')
->inject('servers')
->inject('mode')
- ->inject('authorization')
- ->action(function (Route $route, Request $request, Database $dbForConsole, Document $project, Document $user, ?Document $session, array $servers, string $mode, Authorization $authorization) {
+ ->inject('team')
+ ->action(function (App $utopia, Request $request, Database $dbForConsole, Document $project, Document $user, ?Document $session, array $servers, string $mode, Document $team) {
+ $route = $utopia->getRoute();
+
if ($project->isEmpty()) {
throw new Exception(Exception::PROJECT_NOT_FOUND);
}
- /**
- * ACL Check
- */
+ /** Default role */
+ $roles = Config::getParam('roles', []);
$role = ($user->isEmpty())
? Role::guests()->toString()
: Role::users()->toString();
- // Add user roles
- $memberships = $user->find('teamId', $project->getAttribute('teamId'), 'memberships');
-
- if ($memberships) {
- foreach ($memberships->getAttribute('roles', []) as $memberRole) {
- switch ($memberRole) {
- case 'owner':
- $role = Auth::USER_ROLE_OWNER;
- break;
- case 'admin':
- $role = Auth::USER_ROLE_ADMIN;
- break;
- case 'developer':
- $role = Auth::USER_ROLE_DEVELOPER;
- break;
- }
- }
- }
-
- $roles = Config::getParam('roles', []);
- $scope = $route->getLabel('scope', 'none'); // Allowed scope for chosen route
- $scopes = $roles[$role]['scopes']; // Allowed scopes for user role
+ /** Allowed Scopes for the role */
+ $scopes = $roles[$role]['scopes'];
$apiKey = $request->getHeader('x-appwrite-key', '');
@@ -243,8 +221,8 @@ Http::init()
$role = Auth::USER_ROLE_APPS;
$scopes = \array_merge($roles[$role]['scopes'], $tokenScopes);
- $authorization->addRole(Auth::USER_ROLE_APPS);
- $authorization->setDefaultStatus(false); // Cancel security segmentation for API keys.
+ Authorization::setRole(Auth::USER_ROLE_APPS);
+ Authorization::setDefaultStatus(false); // Cancel security segmentation for API keys.
}
} elseif ($keyType === API_KEY_STANDARD) {
// No underline means no prefix. Backwards compatibility.
@@ -269,8 +247,8 @@ Http::init()
throw new Exception(Exception::PROJECT_KEY_EXPIRED);
}
- $authorization->addRole(Auth::USER_ROLE_APPS);
- $authorization->setDefaultStatus(false); // Cancel security segmentation for API keys.
+ Authorization::setRole(Auth::USER_ROLE_APPS);
+ Authorization::setDefaultStatus(false); // Cancel security segmentation for API keys.
$accessedAt = $key->getAttribute('accessedAt', '');
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_KEY_ACCESS)) > $accessedAt) {
@@ -296,31 +274,56 @@ Http::init()
}
}
}
+ // Admin User Authentication
+ elseif (($project->getId() === 'console' && !$team->isEmpty() && !$user->isEmpty()) || ($project->getId() !== 'console' && !$user->isEmpty() && $mode === APP_MODE_ADMIN)) {
+ $teamId = $team->getId();
+ $adminRoles = [];
+ $memberships = $user->getAttribute('memberships', []);
+ foreach ($memberships as $membership) {
+ if ($membership->getAttribute('confirm', false) === true && $membership->getAttribute('teamId') === $teamId) {
+ $adminRoles = $membership->getAttribute('roles', []);
+ break;
+ }
+ }
- $authorization->addRole($role);
+ if (empty($adminRoles)) {
+ throw new Exception(Exception::USER_UNAUTHORIZED);
+ }
- foreach (Auth::getRoles($user, $authorization) as $authRole) {
- $authorization->addRole($authRole);
+ $scopes = []; // reset scope if admin
+ foreach ($adminRoles as $role) {
+ $scopes = \array_merge($scopes, $roles[$role]['scopes']);
+ }
+
+ Authorization::setDefaultStatus(false); // Cancel security segmentation for admin users.
}
+ $scopes = \array_unique($scopes);
+
+ Authorization::setRole($role);
+ foreach (Auth::getRoles($user) as $authRole) {
+ Authorization::setRole($authRole);
+ }
+
+ /** Do not allow access to disabled services */
$service = $route->getLabel('sdk.namespace', '');
if (!empty($service)) {
if (
array_key_exists($service, $project->getAttribute('services', []))
&& !$project->getAttribute('services', [])[$service]
- && !(Auth::isPrivilegedUser($authorization->getRoles()) || Auth::isAppUser($authorization->getRoles()))
+ && !(Auth::isPrivilegedUser(Authorization::getRoles()) || Auth::isAppUser(Authorization::getRoles()))
) {
throw new Exception(Exception::GENERAL_SERVICE_DISABLED);
}
}
- if (!\in_array($scope, $scopes)) {
- if ($project->isEmpty()) { // Check if permission is denied because project is missing
- throw new Exception(Exception::PROJECT_NOT_FOUND);
- }
+ /** Do now allow access if scope is not allowed */
+ $scope = $route->getLabel('scope', 'none');
+ if (!\in_array($scope, $scopes)) {
throw new Exception(Exception::GENERAL_UNAUTHORIZED_SCOPE, $user->getAttribute('email', 'User') . ' (role: ' . \strtolower($roles[$role]['label']) . ') missing scope (' . $scope . ')');
}
+ /** Do not allow access to blocked accounts */
if (false === $user->getAttribute('status')) { // Account is blocked
throw new Exception(Exception::USER_BLOCKED);
}
@@ -343,9 +346,9 @@ Http::init()
}
});
-Http::init()
+App::init()
->groups(['api'])
- ->inject('route')
+ ->inject('utopia')
->inject('request')
->inject('response')
->inject('project')
@@ -359,12 +362,14 @@ Http::init()
->inject('queueForUsage')
->inject('dbForProject')
->inject('mode')
- ->inject('authorization')
- ->action(function (Route $route, Request $request, Response $response, Document $project, Document $user, Event $queueForEvents, Messaging $queueForMessaging, Audit $queueForAudits, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, Usage $queueForUsage, Database $dbForProject, string $mode, Authorization $authorization) use ($databaseListener) {
+ ->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Event $queueForEvents, Messaging $queueForMessaging, Audit $queueForAudits, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, Usage $queueForUsage, Database $dbForProject, string $mode) use ($databaseListener) {
+
+ $route = $utopia->getRoute();
+
if (
array_key_exists('rest', $project->getAttribute('apis', []))
&& !$project->getAttribute('apis', [])['rest']
- && !(Auth::isPrivilegedUser($authorization->getRoles()) || Auth::isAppUser($authorization->getRoles()))
+ && !(Auth::isPrivilegedUser(Authorization::getRoles()) || Auth::isAppUser(Authorization::getRoles()))
) {
throw new AppwriteException(AppwriteException::GENERAL_API_DISABLED);
}
@@ -394,7 +399,7 @@ Http::init()
$closestLimit = null;
- $roles = $authorization->getRoles();
+ $roles = Authorization::getRoles();
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
$isAppUser = Auth::isAppUser($roles);
@@ -458,7 +463,7 @@ Http::init()
$useCache = $route->getLabel('cache', false);
if ($useCache) {
$key = md5($request->getURI() . '*' . implode('*', $request->getParams()) . '*' . APP_CACHE_BUSTER);
- $cacheLog = $authorization->skip(fn () => $dbForProject->getDocument('cache', $key));
+ $cacheLog = Authorization::skip(fn () => $dbForProject->getDocument('cache', $key));
$cache = new Cache(
new Filesystem(APP_STORAGE_CACHE . DIRECTORY_SEPARATOR . 'app-' . $project->getId())
);
@@ -471,18 +476,19 @@ Http::init()
if ($type === 'bucket') {
$bucketId = $parts[1] ?? null;
+ $bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
- $bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
-
- $isAPIKey = Auth::isAppUser($authorization->getRoles());
- $isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles());
+ $isAPIKey = Auth::isAppUser(Authorization::getRoles());
+ $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) {
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
}
$fileSecurity = $bucket->getAttribute('fileSecurity', false);
- $valid = $authorization->isValid(new Input(Database::PERMISSION_READ, $bucket->getRead()));
+ $validator = new Authorization(Database::PERMISSION_READ);
+ $valid = $validator->isValid($bucket->getRead());
+
if (!$fileSecurity && !$valid) {
throw new Exception(Exception::USER_UNAUTHORIZED);
}
@@ -493,7 +499,7 @@ Http::init()
if ($fileSecurity && !$valid) {
$file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
} else {
- $file = $authorization->skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
+ $file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
}
if ($file->isEmpty()) {
@@ -517,7 +523,7 @@ Http::init()
}
});
-Http::init()
+App::init()
->groups(['session'])
->inject('user')
->inject('request')
@@ -537,12 +543,14 @@ Http::init()
* Delete older sessions if the number of sessions have crossed
* the session limit set for the project
*/
-Http::shutdown()
+App::shutdown()
->groups(['session'])
+ ->inject('utopia')
+ ->inject('request')
->inject('response')
->inject('project')
->inject('dbForProject')
- ->action(function (Response $response, Document $project, Database $dbForProject) {
+ ->action(function (App $utopia, Request $request, Response $response, Document $project, Database $dbForProject) {
$sessionLimit = $project->getAttribute('auths', [])['maxSessions'] ?? APP_LIMIT_USER_SESSIONS_DEFAULT;
$session = $response->getPayload();
$userId = $session['userId'] ?? '';
@@ -569,9 +577,9 @@ Http::shutdown()
$dbForProject->purgeCachedDocument('users', $userId);
});
-Http::shutdown()
+App::shutdown()
->groups(['api'])
- ->inject('route')
+ ->inject('utopia')
->inject('request')
->inject('response')
->inject('project')
@@ -587,29 +595,7 @@ Http::shutdown()
->inject('queueForFunctions')
->inject('mode')
->inject('dbForConsole')
- ->inject('authorization')
- ->action(function (
- Route $route,
- Request $request,
- Response $response,
- Document $project,
- Document $user,
- Event $queueForEvents,
- Audit $queueForAudits,
- Usage $queueForUsage,
- Delete $queueForDeletes,
- EventDatabase $queueForDatabase,
- Build $queueForBuilds,
- Messaging $queueForMessaging,
- Database $dbForProject,
- Func $queueForFunctions,
- string $mode,
- Database $dbForConsole,
- Authorization $authorization,
- ) use ($parseLabel) {
- if (!empty($user) && !$user->isEmpty() && empty($user->getInternalId())) {
- $user = $authorization->skip(fn () => $dbForProject->getDocument('users', $user->getId()));
- }
+ ->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Event $queueForEvents, Audit $queueForAudits, Usage $queueForUsage, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, Messaging $queueForMessaging, Database $dbForProject, Func $queueForFunctions, string $mode, Database $dbForConsole) use ($parseLabel) {
$responsePayload = $response->getPayload();
@@ -621,10 +607,11 @@ Http::shutdown()
/**
* Trigger functions.
*/
- $queueForFunctions
- ->from($queueForEvents)
- ->trigger();
-
+ if (!$queueForEvents->isPaused()) {
+ $queueForFunctions
+ ->from($queueForEvents)
+ ->trigger();
+ }
/**
* Trigger webhooks.
*/
@@ -668,6 +655,7 @@ Http::shutdown()
}
}
+ $route = $utopia->getRoute();
$requestParams = $route->getParamsValues();
/**
@@ -737,11 +725,11 @@ Http::shutdown()
$key = md5($request->getURI() . '*' . implode('*', $request->getParams()) . '*' . APP_CACHE_BUSTER);
$signature = md5($data['payload']);
- $cacheLog = $authorization->skip(fn () => $dbForProject->getDocument('cache', $key));
+ $cacheLog = Authorization::skip(fn () => $dbForProject->getDocument('cache', $key));
$accessedAt = $cacheLog->getAttribute('accessedAt', '');
$now = DateTime::now();
if ($cacheLog->isEmpty()) {
- $authorization->skip(fn () => $dbForProject->createDocument('cache', new Document([
+ Authorization::skip(fn () => $dbForProject->createDocument('cache', new Document([
'$id' => $key,
'resource' => $resource,
'resourceType' => $resourceType,
@@ -751,7 +739,7 @@ Http::shutdown()
])));
} elseif (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_CACHE_UPDATE)) > $accessedAt) {
$cacheLog->setAttribute('accessedAt', $now);
- $authorization->skip(fn () => $dbForProject->updateDocument('cache', $cacheLog->getId(), $cacheLog));
+ Authorization::skip(fn () => $dbForProject->updateDocument('cache', $cacheLog->getId(), $cacheLog));
}
if ($signature !== $cacheLog->getAttribute('signature')) {
@@ -763,8 +751,10 @@ Http::shutdown()
}
}
+
+
if ($project->getId() !== 'console') {
- if (!Auth::isPrivilegedUser($authorization->getRoles())) {
+ if (!Auth::isPrivilegedUser(Authorization::getRoles())) {
$fileSize = 0;
$file = $request->getFiles('file');
if (!empty($file)) {
@@ -789,7 +779,7 @@ Http::shutdown()
$accessedAt = $project->getAttribute('accessedAt', '');
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_PROJECT_ACCESS)) > $accessedAt) {
$project->setAttribute('accessedAt', DateTime::now());
- $authorization->skip(fn () => $dbForConsole->updateDocument('projects', $project->getId(), $project));
+ Authorization::skip(fn () => $dbForConsole->updateDocument('projects', $project->getId(), $project));
}
}
@@ -810,16 +800,10 @@ Http::shutdown()
}
});
-Http::init()
+App::init()
->groups(['usage'])
->action(function () {
if (System::getEnv('_APP_USAGE_STATS', 'enabled') !== 'enabled') {
throw new Exception(Exception::GENERAL_USAGE_DISABLED);
}
});
-
-Http::shutdown()
- ->inject('connections')
- ->action(function (Connections $connections) {
- $connections->reclaim();
- });
diff --git a/app/controllers/shared/api/auth.php b/app/controllers/shared/api/auth.php
index 224dcb3392..53aacabe21 100644
--- a/app/controllers/shared/api/auth.php
+++ b/app/controllers/shared/api/auth.php
@@ -4,14 +4,13 @@ use Appwrite\Auth\Auth;
use Appwrite\Extend\Exception;
use Appwrite\Utopia\Request;
use MaxMind\Db\Reader;
+use Utopia\App;
use Utopia\Database\DateTime;
use Utopia\Database\Document;
use Utopia\Database\Validator\Authorization;
-use Utopia\Http\Http;
-use Utopia\Http\Route;
use Utopia\System\System;
-Http::init()
+App::init()
->groups(['mfaProtected'])
->inject('session')
->action(function (Document $session) {
@@ -30,14 +29,13 @@ Http::init()
}
});
-Http::init()
+App::init()
->groups(['auth'])
- ->inject('route')
+ ->inject('utopia')
->inject('request')
->inject('project')
->inject('geodb')
- ->inject('authorization')
- ->action(function (Route $route, Request $request, Document $project, Reader $geodb, Authorization $authorization) {
+ ->action(function (App $utopia, Request $request, Document $project, Reader $geodb) {
$denylist = System::getEnv('_APP_CONSOLE_COUNTRIES_DENYLIST', '');
if (!empty($denylist && $project->getId() === 'console')) {
$countries = explode(',', $denylist);
@@ -48,17 +46,15 @@ Http::init()
}
}
- $isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles());
- $isAppUser = Auth::isAppUser($authorization->getRoles());
+ $route = $utopia->match($request);
+
+ $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
+ $isAppUser = Auth::isAppUser(Authorization::getRoles());
if ($isAppUser || $isPrivilegedUser) { // Skip limits for app and console devs
return;
}
- if ($route->getLabel('sdk.namespace', '') === 'graphql') { // Skip for graphQL recursive call
- return;
- }
-
$auths = $project->getAttribute('auths', []);
switch ($route->getLabel('auth.type', '')) {
case 'emailPassword':
diff --git a/app/controllers/web/console.php b/app/controllers/web/console.php
index 60be4acf54..c02e140270 100644
--- a/app/controllers/web/console.php
+++ b/app/controllers/web/console.php
@@ -2,9 +2,9 @@
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response;
-use Utopia\Http\Http;
+use Utopia\App;
-Http::init()
+App::init()
->groups(['web'])
->inject('request')
->inject('response')
@@ -16,7 +16,7 @@ Http::init()
;
});
-Http::get('/')
+App::get('/')
->alias('auth/*')
->alias('/invite')
->alias('/login')
diff --git a/app/controllers/web/home.php b/app/controllers/web/home.php
index 3577061d69..27b2614c37 100644
--- a/app/controllers/web/home.php
+++ b/app/controllers/web/home.php
@@ -1,10 +1,10 @@
desc('Get Version')
->groups(['home', 'web'])
->label('scope', 'public')
diff --git a/app/http.php b/app/http.php
index 320621ad33..bec772c770 100644
--- a/app/http.php
+++ b/app/http.php
@@ -1,242 +1,335 @@
true,
- 'http_compression' => true,
- 'http_compression_level' => 6,
- 'package_max_length' => $payloadSize,
- 'buffer_output_size' => $payloadSize,
- // Server
- // 'log_level' => 0,
- 'dispatch_mode' => 2,
- 'worker_num' => $workerNumber,
- 'reactor_num' => swoole_cpu_num() * 2,
- 'open_cpu_affinity' => true,
- // Coroutine
- 'enable_coroutine' => true,
- 'send_yield' => true,
- 'tcp_fastopen' => true,
-]);
+$http
+ ->set([
+ 'worker_num' => $workerNumber,
+ 'open_http2_protocol' => true,
+ 'http_compression' => true,
+ 'http_compression_level' => 6,
+ 'package_max_length' => $payloadSize,
+ 'buffer_output_size' => $payloadSize,
+ ]);
-$http = new Http($server, $container, 'UTC');
-$http->setRequestClass(Request::class);
-$http->setResponseClass(Response::class);
+$http->on(Constant::EVENT_WORKER_START, function ($server, $workerId) {
+ Console::success('Worker ' . ++$workerId . ' started successfully');
+});
-Http::onStart()
- ->inject('authorization')
- ->inject('cache')
- ->inject('pools')
- ->inject('connections')
- ->action(function (Authorization $authorization, Cache $cache, array $pools, Connections $connections) {
- try {
- // wait for database to be ready
- $attempts = 0;
- $max = 15;
- $sleep = 2;
+$http->on(Constant::EVENT_BEFORE_RELOAD, function ($server, $workerId) {
+ Console::success('Starting reload...');
+});
- do {
- try {
- $attempts++;
- $pool = $pools['pools-console-console']['pool'];
- $dsn = $pools['pools-console-console']['dsn'];
- $connection = $pool->get();
- $connections->add($connection, $pool);
+$http->on(Constant::EVENT_AFTER_RELOAD, function ($server, $workerId) {
+ Console::success('Reload completed...');
+});
- $adapter = match ($dsn->getScheme()) {
- 'mariadb' => new MariaDB($connection),
- 'mysql' => new MySQL($connection),
- default => null
- };
+include __DIR__ . '/controllers/general.php';
- $adapter->setDatabase($dsn->getPath());
+$http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $register) {
+ $app = new App('UTC');
- $dbForConsole = new Database($adapter, $cache);
- $dbForConsole->setAuthorization($authorization);
+ go(function () use ($register, $app) {
+ $pools = $register->get('pools');
+ /** @var Group $pools */
+ App::setResource('pools', fn () => $pools);
- $dbForConsole
- ->setNamespace('_console')
- ->setMetadata('host', \gethostname())
- ->setMetadata('project', 'console')
- ->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS);
+ // wait for database to be ready
+ $attempts = 0;
+ $max = 10;
+ $sleep = 1;
- $dbForConsole->ping();
- break; // leave the do-while if successful
- } catch (\Throwable $e) {
- Console::warning("Database not ready. Retrying connection ({$attempts})...");
- if ($attempts >= $max) {
- throw new \Exception('Failed to connect to database: ' . $e->getMessage());
- }
- sleep($sleep);
+ do {
+ try {
+ $attempts++;
+ $dbForConsole = $app->getResource('dbForConsole');
+ /** @var Utopia\Database\Database $dbForConsole */
+ break; // leave the do-while if successful
+ } catch (\Throwable $e) {
+ Console::warning("Database not ready. Retrying connection ({$attempts})...");
+ if ($attempts >= $max) {
+ throw new \Exception('Failed to connect to database: ' . $e->getMessage());
}
- } while ($attempts < $max);
+ sleep($sleep);
+ }
+ } while ($attempts < $max);
- Console::success('[Setup] - Server database init started...');
+ Console::success('[Setup] - Server database init started...');
+
+ try {
+ Console::success('[Setup] - Creating database: appwrite...');
+ $dbForConsole->create();
+ } catch (\Throwable $e) {
+ Console::success('[Setup] - Skip: metadata table already exists');
+ }
+
+ if ($dbForConsole->getCollection(Audit::COLLECTION)->isEmpty()) {
+ $audit = new Audit($dbForConsole);
+ $audit->setup();
+ }
+
+ if ($dbForConsole->getCollection(TimeLimit::COLLECTION)->isEmpty()) {
+ $adapter = new TimeLimit("", 0, 1, $dbForConsole);
+ $adapter->setup();
+ }
+
+ /** @var array $collections */
+ $collections = Config::getParam('collections', []);
+ $consoleCollections = $collections['console'];
+ foreach ($consoleCollections as $key => $collection) {
+ if (($collection['$collection'] ?? '') !== Database::METADATA) {
+ continue;
+ }
+ if (!$dbForConsole->getCollection($key)->isEmpty()) {
+ continue;
+ }
+
+ Console::success('[Setup] - Creating collection: ' . $collection['$id'] . '...');
+
+ $attributes = [];
+ $indexes = [];
+
+ foreach ($collection['attributes'] as $attribute) {
+ $attributes[] = new Document([
+ '$id' => ID::custom($attribute['$id']),
+ 'type' => $attribute['type'],
+ 'size' => $attribute['size'],
+ 'required' => $attribute['required'],
+ 'signed' => $attribute['signed'],
+ 'array' => $attribute['array'],
+ 'filters' => $attribute['filters'],
+ 'default' => $attribute['default'] ?? null,
+ 'format' => $attribute['format'] ?? ''
+ ]);
+ }
+
+ foreach ($collection['indexes'] as $index) {
+ $indexes[] = new Document([
+ '$id' => ID::custom($index['$id']),
+ 'type' => $index['type'],
+ 'attributes' => $index['attributes'],
+ 'lengths' => $index['lengths'],
+ 'orders' => $index['orders'],
+ ]);
+ }
+
+ $dbForConsole->createCollection($key, $attributes, $indexes);
+ }
+
+ if ($dbForConsole->getDocument('buckets', 'default')->isEmpty() && !$dbForConsole->exists($dbForConsole->getDatabase(), 'bucket_1')) {
+ Console::success('[Setup] - Creating default bucket...');
+ $dbForConsole->createDocument('buckets', new Document([
+ '$id' => ID::custom('default'),
+ '$collection' => ID::custom('buckets'),
+ 'name' => 'Default',
+ 'maximumFileSize' => (int) System::getEnv('_APP_STORAGE_LIMIT', 0), // 10MB
+ 'allowedFileExtensions' => [],
+ 'enabled' => true,
+ 'compression' => 'gzip',
+ 'encryption' => true,
+ 'antivirus' => true,
+ 'fileSecurity' => true,
+ '$permissions' => [
+ Permission::create(Role::any()),
+ Permission::read(Role::any()),
+ Permission::update(Role::any()),
+ Permission::delete(Role::any()),
+ ],
+ 'search' => 'buckets Default',
+ ]));
+
+ $bucket = $dbForConsole->getDocument('buckets', 'default');
+
+ Console::success('[Setup] - Creating files collection for default bucket...');
+ $files = $collections['buckets']['files'] ?? [];
+ if (empty($files)) {
+ throw new Exception('Files collection is not configured.');
+ }
+
+ $attributes = [];
+ $indexes = [];
+
+ foreach ($files['attributes'] as $attribute) {
+ $attributes[] = new Document([
+ '$id' => ID::custom($attribute['$id']),
+ 'type' => $attribute['type'],
+ 'size' => $attribute['size'],
+ 'required' => $attribute['required'],
+ 'signed' => $attribute['signed'],
+ 'array' => $attribute['array'],
+ 'filters' => $attribute['filters'],
+ 'default' => $attribute['default'] ?? null,
+ 'format' => $attribute['format'] ?? ''
+ ]);
+ }
+
+ foreach ($files['indexes'] as $index) {
+ $indexes[] = new Document([
+ '$id' => ID::custom($index['$id']),
+ 'type' => $index['type'],
+ 'attributes' => $index['attributes'],
+ 'lengths' => $index['lengths'],
+ 'orders' => $index['orders'],
+ ]);
+ }
+
+ $dbForConsole->createCollection('bucket_' . $bucket->getInternalId(), $attributes, $indexes);
+ }
+
+ $pools->reclaim();
+
+ Console::success('[Setup] - Server database init completed...');
+ });
+
+ Console::success('Server started successfully (max payload is ' . number_format($payloadSize) . ' bytes)');
+ Console::info("Master pid {$http->master_pid}, manager pid {$http->manager_pid}");
+
+ // listen ctrl + c
+ Process::signal(2, function () use ($http) {
+ Console::log('Stop by Ctrl+C');
+ $http->shutdown();
+ });
+});
+
+$http->on('request', function (SwooleRequest $swooleRequest, SwooleResponse $swooleResponse) use ($register) {
+ App::setResource('swooleRequest', fn () => $swooleRequest);
+ App::setResource('swooleResponse', fn () => $swooleResponse);
+
+ $request = new Request($swooleRequest);
+ $response = new Response($swooleResponse);
+
+ if (Files::isFileLoaded($request->getURI())) {
+ $time = (60 * 60 * 24 * 365 * 2); // 45 days cache
+
+ $response
+ ->setContentType(Files::getFileMimeType($request->getURI()))
+ ->addHeader('Cache-Control', 'public, max-age=' . $time)
+ ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + $time) . ' GMT') // 45 days cache
+ ->send(Files::getFileContents($request->getURI()));
+
+ return;
+ }
+
+ $app = new App('UTC');
+
+ $pools = $register->get('pools');
+ App::setResource('pools', fn () => $pools);
+
+ try {
+ Authorization::cleanRoles();
+ Authorization::setRole(Role::any()->toString());
+
+ $app->run($request, $response);
+ } catch (\Throwable $th) {
+ $version = System::getEnv('_APP_VERSION', 'UNKNOWN');
+
+ $logger = $app->getResource("logger");
+ if ($logger) {
+ try {
+ /** @var Utopia\Database\Document $user */
+ $user = $app->getResource('user');
+ } catch (\Throwable $_th) {
+ // All good, user is optional information for logger
+ }
+
+ $route = $app->getRoute();
+
+ $log = $app->getResource("log");
+
+ if (isset($user) && !$user->isEmpty()) {
+ $log->setUser(new User($user->getId()));
+ }
+
+ $log->setNamespace("http");
+ $log->setServer(\gethostname());
+ $log->setVersion($version);
+ $log->setType(Log::TYPE_ERROR);
+ $log->setMessage($th->getMessage());
+
+ $log->addTag('method', $route->getMethod());
+ $log->addTag('url', $route->getPath());
+ $log->addTag('verboseType', get_class($th));
+ $log->addTag('code', $th->getCode());
+ // $log->addTag('projectId', $project->getId()); // TODO: Figure out how to get ProjectID, if it becomes relevant
+ $log->addTag('hostname', $request->getHostname());
+ $log->addTag('locale', (string)$request->getParam('locale', $request->getHeader('x-appwrite-locale', '')));
+
+ $log->addExtra('file', $th->getFile());
+ $log->addExtra('line', $th->getLine());
+ $log->addExtra('trace', $th->getTraceAsString());
+ $log->addExtra('roles', Authorization::getRoles());
+
+ $action = $route->getLabel("sdk.namespace", "UNKNOWN_NAMESPACE") . '.' . $route->getLabel("sdk.method", "UNKNOWN_METHOD");
+ $log->setAction($action);
+
+ $isProduction = System::getEnv('_APP_ENV', 'development') === 'production';
+ $log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING);
try {
- Console::success('[Setup] - Creating database: appwrite...');
- $dbForConsole->create();
- } catch (\Throwable $e) {
- Console::success('[Setup] - Skip: metadata table already exists');
- return true;
+ $responseCode = $logger->addLog($log);
+ Console::info('Error log pushed with status code: ' . $responseCode);
+ } catch (Throwable $th) {
+ Console::error('Error pushing log: ' . $th->getMessage());
}
-
- if ($dbForConsole->getCollection(Audit::COLLECTION)->isEmpty()) {
- $audit = new Audit($dbForConsole);
- $audit->setup();
- }
-
- if ($dbForConsole->getCollection(TimeLimit::COLLECTION)->isEmpty()) {
- $abuse = new TimeLimit("", 0, 1, $dbForConsole);
- $abuse->setup();
- }
-
- /** @var array $collections */
- $collections = Config::getParam('collections', []);
- $consoleCollections = $collections['console'];
- foreach ($consoleCollections as $key => $collection) {
- if (($collection['$collection'] ?? '') !== Database::METADATA) {
- continue;
- }
- if (!$dbForConsole->getCollection($key)->isEmpty()) {
- continue;
- }
-
- Console::success('[Setup] - Creating collection: ' . $collection['$id'] . '...');
-
- $attributes = [];
- $indexes = [];
-
- foreach ($collection['attributes'] as $attribute) {
- $attributes[] = new Document([
- '$id' => ID::custom($attribute['$id']),
- 'type' => $attribute['type'],
- 'size' => $attribute['size'],
- 'required' => $attribute['required'],
- 'signed' => $attribute['signed'],
- 'array' => $attribute['array'],
- 'filters' => $attribute['filters'],
- 'default' => $attribute['default'] ?? null,
- 'format' => $attribute['format'] ?? ''
- ]);
- }
-
- foreach ($collection['indexes'] as $index) {
- $indexes[] = new Document([
- '$id' => ID::custom($index['$id']),
- 'type' => $index['type'],
- 'attributes' => $index['attributes'],
- 'lengths' => $index['lengths'],
- 'orders' => $index['orders'],
- ]);
- }
-
- $dbForConsole->createCollection($key, $attributes, $indexes);
- }
-
- if ($dbForConsole->getDocument('buckets', 'default')->isEmpty() && !$dbForConsole->exists($dbForConsole->getDatabase(), 'bucket_1')) {
- Console::success('[Setup] - Creating default bucket...');
- $dbForConsole->createDocument('buckets', new Document([
- '$id' => ID::custom('default'),
- '$collection' => ID::custom('buckets'),
- 'name' => 'Default',
- 'maximumFileSize' => (int) System::getEnv('_APP_STORAGE_LIMIT', 0), // 10MB
- 'allowedFileExtensions' => [],
- 'enabled' => true,
- 'compression' => 'gzip',
- 'encryption' => true,
- 'antivirus' => true,
- 'fileSecurity' => true,
- '$permissions' => [
- Permission::create(Role::any()),
- Permission::read(Role::any()),
- Permission::update(Role::any()),
- Permission::delete(Role::any()),
- ],
- 'search' => 'buckets Default',
- ]));
-
- $bucket = $dbForConsole->getDocument('buckets', 'default');
-
- Console::success('[Setup] - Creating files collection for default bucket...');
-
- $files = $collections['buckets']['files'] ?? [];
- if (empty($files)) {
- throw new Exception('Files collection is not configured.');
- }
-
- $attributes = [];
- $indexes = [];
-
- foreach ($files['attributes'] as $attribute) {
- $attributes[] = new Document([
- '$id' => ID::custom($attribute['$id']),
- 'type' => $attribute['type'],
- 'size' => $attribute['size'],
- 'required' => $attribute['required'],
- 'signed' => $attribute['signed'],
- 'array' => $attribute['array'],
- 'filters' => $attribute['filters'],
- 'default' => $attribute['default'] ?? null,
- 'format' => $attribute['format'] ?? ''
- ]);
- }
-
- foreach ($files['indexes'] as $index) {
- $indexes[] = new Document([
- '$id' => ID::custom($index['$id']),
- 'type' => $index['type'],
- 'attributes' => $index['attributes'],
- 'lengths' => $index['lengths'],
- 'orders' => $index['orders'],
- ]);
- }
-
- $dbForConsole->createCollection('bucket_' . $bucket->getInternalId(), $attributes, $indexes);
- }
-
- $connections->reclaim();
-
- Console::success('[Setup] - Server database init completed...');
- Console::success('Server started successfully');
- } catch (\Throwable $e) {
- Console::warning('Database not ready: ' . $e->getMessage());
- exit(1);
}
- });
-Http::init()
- ->inject('authorization')
- ->action(function (Authorization $authorization) {
- $authorization->cleanRoles();
- $authorization->addRole(Role::any()->toString());
- });
+ Console::error('[Error] Type: ' . get_class($th));
+ Console::error('[Error] Message: ' . $th->getMessage());
+ Console::error('[Error] File: ' . $th->getFile());
+ Console::error('[Error] Line: ' . $th->getLine());
+
+ $swooleResponse->setStatusCode(500);
+
+ $output = ((App::isDevelopment())) ? [
+ 'message' => 'Error: ' . $th->getMessage(),
+ 'code' => 500,
+ 'file' => $th->getFile(),
+ 'line' => $th->getLine(),
+ 'trace' => $th->getTrace(),
+ 'version' => $version,
+ ] : [
+ 'message' => 'Error: Server Error',
+ 'code' => 500,
+ 'version' => $version,
+ ];
+
+ $swooleResponse->end(\json_encode($output));
+ } finally {
+ $pools->reclaim();
+ }
+});
$http->start();
diff --git a/app/init.php b/app/init.php
index cb97be80fe..c2777f36f1 100644
--- a/app/init.php
+++ b/app/init.php
@@ -1,14 +1,26 @@
getScheme();
- $accessKey = $dsn->getUser() ?? '';
- $accessSecret = $dsn->getPassword() ?? '';
- $bucket = $dsn->getPath() ?? '';
- $region = $dsn->getParam('region');
- } catch (\Throwable $e) {
- Console::warning($e->getMessage() . 'Invalid DSN. Defaulting to Local device.');
+/**
+ * New DB Filters
+ */
+Database::addFilter(
+ 'casting',
+ function (mixed $value) {
+ return json_encode(['value' => $value], JSON_PRESERVE_ZERO_FRACTION);
+ },
+ function (mixed $value) {
+ if (is_null($value)) {
+ return;
}
- switch ($device) {
- case Storage::DEVICE_S3:
- return new S3($root, $accessKey, $accessSecret, $bucket, $region, $acl);
- case STORAGE::DEVICE_DO_SPACES:
- return new DOSpaces($root, $accessKey, $accessSecret, $bucket, $region, $acl);
- case Storage::DEVICE_BACKBLAZE:
- return new Backblaze($root, $accessKey, $accessSecret, $bucket, $region, $acl);
- case Storage::DEVICE_LINODE:
- return new Linode($root, $accessKey, $accessSecret, $bucket, $region, $acl);
- case Storage::DEVICE_WASABI:
- return new Wasabi($root, $accessKey, $accessSecret, $bucket, $region, $acl);
- case Storage::DEVICE_LOCAL:
- default:
- return new Local($root);
- }
- } else {
- switch (strtolower(System::getEnv('_APP_STORAGE_DEVICE', Storage::DEVICE_LOCAL) ?? '')) {
- case Storage::DEVICE_LOCAL:
- default:
- return new Local($root);
- case Storage::DEVICE_S3:
- $s3AccessKey = System::getEnv('_APP_STORAGE_S3_ACCESS_KEY', '');
- $s3SecretKey = System::getEnv('_APP_STORAGE_S3_SECRET', '');
- $s3Region = System::getEnv('_APP_STORAGE_S3_REGION', '');
- $s3Bucket = System::getEnv('_APP_STORAGE_S3_BUCKET', '');
- $s3Acl = 'private';
- return new S3($root, $s3AccessKey, $s3SecretKey, $s3Bucket, $s3Region, $s3Acl);
- case Storage::DEVICE_DO_SPACES:
- $doSpacesAccessKey = System::getEnv('_APP_STORAGE_DO_SPACES_ACCESS_KEY', '');
- $doSpacesSecretKey = System::getEnv('_APP_STORAGE_DO_SPACES_SECRET', '');
- $doSpacesRegion = System::getEnv('_APP_STORAGE_DO_SPACES_REGION', '');
- $doSpacesBucket = System::getEnv('_APP_STORAGE_DO_SPACES_BUCKET', '');
- $doSpacesAcl = 'private';
- return new DOSpaces($root, $doSpacesAccessKey, $doSpacesSecretKey, $doSpacesBucket, $doSpacesRegion, $doSpacesAcl);
- case Storage::DEVICE_BACKBLAZE:
- $backblazeAccessKey = System::getEnv('_APP_STORAGE_BACKBLAZE_ACCESS_KEY', '');
- $backblazeSecretKey = System::getEnv('_APP_STORAGE_BACKBLAZE_SECRET', '');
- $backblazeRegion = System::getEnv('_APP_STORAGE_BACKBLAZE_REGION', '');
- $backblazeBucket = System::getEnv('_APP_STORAGE_BACKBLAZE_BUCKET', '');
- $backblazeAcl = 'private';
- return new Backblaze($root, $backblazeAccessKey, $backblazeSecretKey, $backblazeBucket, $backblazeRegion, $backblazeAcl);
- case Storage::DEVICE_LINODE:
- $linodeAccessKey = System::getEnv('_APP_STORAGE_LINODE_ACCESS_KEY', '');
- $linodeSecretKey = System::getEnv('_APP_STORAGE_LINODE_SECRET', '');
- $linodeRegion = System::getEnv('_APP_STORAGE_LINODE_REGION', '');
- $linodeBucket = System::getEnv('_APP_STORAGE_LINODE_BUCKET', '');
- $linodeAcl = 'private';
- return new Linode($root, $linodeAccessKey, $linodeSecretKey, $linodeBucket, $linodeRegion, $linodeAcl);
- case Storage::DEVICE_WASABI:
- $wasabiAccessKey = System::getEnv('_APP_STORAGE_WASABI_ACCESS_KEY', '');
- $wasabiSecretKey = System::getEnv('_APP_STORAGE_WASABI_SECRET', '');
- $wasabiRegion = System::getEnv('_APP_STORAGE_WASABI_REGION', '');
- $wasabiBucket = System::getEnv('_APP_STORAGE_WASABI_BUCKET', '');
- $wasabiAcl = 'private';
- return new Wasabi($root, $wasabiAccessKey, $wasabiSecretKey, $wasabiBucket, $wasabiRegion, $wasabiAcl);
- }
+ return json_decode($value, true)['value'];
}
-}
+);
-$container = new Container();
-$registry = new Registry();
+Database::addFilter(
+ 'enum',
+ function (mixed $value, Document $attribute) {
+ if ($attribute->isSet('elements')) {
+ $attribute->removeAttribute('elements');
+ }
-$registry->set('logger', function () {
+ return $value;
+ },
+ function (mixed $value, Document $attribute) {
+ $formatOptions = \json_decode($attribute->getAttribute('formatOptions', '[]'), true);
+ if (isset($formatOptions['elements'])) {
+ $attribute->setAttribute('elements', $formatOptions['elements']);
+ }
+
+ return $value;
+ }
+);
+
+Database::addFilter(
+ 'range',
+ function (mixed $value, Document $attribute) {
+ if ($attribute->isSet('min')) {
+ $attribute->removeAttribute('min');
+ }
+ if ($attribute->isSet('max')) {
+ $attribute->removeAttribute('max');
+ }
+
+ return $value;
+ },
+ function (mixed $value, Document $attribute) {
+ $formatOptions = json_decode($attribute->getAttribute('formatOptions', '[]'), true);
+ if (isset($formatOptions['min']) || isset($formatOptions['max'])) {
+ $attribute
+ ->setAttribute('min', $formatOptions['min'])
+ ->setAttribute('max', $formatOptions['max']);
+ }
+
+ return $value;
+ }
+);
+
+Database::addFilter(
+ 'subQueryAttributes',
+ function (mixed $value) {
+ return;
+ },
+ function (mixed $value, Document $document, Database $database) {
+ $attributes = $database->find('attributes', [
+ Query::equal('collectionInternalId', [$document->getInternalId()]),
+ Query::equal('databaseInternalId', [$document->getAttribute('databaseInternalId')]),
+ Query::limit($database->getLimitForAttributes()),
+ ]);
+
+ foreach ($attributes as $attribute) {
+ if ($attribute->getAttribute('type') === Database::VAR_RELATIONSHIP) {
+ $options = $attribute->getAttribute('options');
+ foreach ($options as $key => $value) {
+ $attribute->setAttribute($key, $value);
+ }
+ $attribute->removeAttribute('options');
+ }
+ }
+
+ return $attributes;
+ }
+);
+
+Database::addFilter(
+ 'subQueryIndexes',
+ function (mixed $value) {
+ return;
+ },
+ function (mixed $value, Document $document, Database $database) {
+ return $database
+ ->find('indexes', [
+ Query::equal('collectionInternalId', [$document->getInternalId()]),
+ Query::equal('databaseInternalId', [$document->getAttribute('databaseInternalId')]),
+ Query::limit($database->getLimitForIndexes()),
+ ]);
+ }
+);
+
+Database::addFilter(
+ 'subQueryPlatforms',
+ function (mixed $value) {
+ return;
+ },
+ function (mixed $value, Document $document, Database $database) {
+ return $database
+ ->find('platforms', [
+ Query::equal('projectInternalId', [$document->getInternalId()]),
+ Query::limit(APP_LIMIT_SUBQUERY),
+ ]);
+ }
+);
+
+Database::addFilter(
+ 'subQueryKeys',
+ function (mixed $value) {
+ return;
+ },
+ function (mixed $value, Document $document, Database $database) {
+ return $database
+ ->find('keys', [
+ Query::equal('projectInternalId', [$document->getInternalId()]),
+ Query::limit(APP_LIMIT_SUBQUERY),
+ ]);
+ }
+);
+
+Database::addFilter(
+ 'subQueryWebhooks',
+ function (mixed $value) {
+ return;
+ },
+ function (mixed $value, Document $document, Database $database) {
+ return $database
+ ->find('webhooks', [
+ Query::equal('projectInternalId', [$document->getInternalId()]),
+ Query::limit(APP_LIMIT_SUBQUERY),
+ ]);
+ }
+);
+
+Database::addFilter(
+ 'subQuerySessions',
+ function (mixed $value) {
+ return;
+ },
+ function (mixed $value, Document $document, Database $database) {
+ return Authorization::skip(fn () => $database->find('sessions', [
+ Query::equal('userInternalId', [$document->getInternalId()]),
+ Query::limit(APP_LIMIT_SUBQUERY),
+ ]));
+ }
+);
+
+Database::addFilter(
+ 'subQueryTokens',
+ function (mixed $value) {
+ return;
+ },
+ function (mixed $value, Document $document, Database $database) {
+ return Authorization::skip(fn () => $database
+ ->find('tokens', [
+ Query::equal('userInternalId', [$document->getInternalId()]),
+ Query::limit(APP_LIMIT_SUBQUERY),
+ ]));
+ }
+);
+
+Database::addFilter(
+ 'subQueryChallenges',
+ function (mixed $value) {
+ return;
+ },
+ function (mixed $value, Document $document, Database $database) {
+ return Authorization::skip(fn () => $database
+ ->find('challenges', [
+ Query::equal('userInternalId', [$document->getInternalId()]),
+ Query::limit(APP_LIMIT_SUBQUERY),
+ ]));
+ }
+);
+
+Database::addFilter(
+ 'subQueryAuthenticators',
+ function (mixed $value) {
+ return;
+ },
+ function (mixed $value, Document $document, Database $database) {
+ return Authorization::skip(fn () => $database
+ ->find('authenticators', [
+ Query::equal('userInternalId', [$document->getInternalId()]),
+ Query::limit(APP_LIMIT_SUBQUERY),
+ ]));
+ }
+);
+
+Database::addFilter(
+ 'subQueryMemberships',
+ function (mixed $value) {
+ return;
+ },
+ function (mixed $value, Document $document, Database $database) {
+ return Authorization::skip(fn () => $database
+ ->find('memberships', [
+ Query::equal('userInternalId', [$document->getInternalId()]),
+ Query::limit(APP_LIMIT_SUBQUERY),
+ ]));
+ }
+);
+
+Database::addFilter(
+ 'subQueryVariables',
+ function (mixed $value) {
+ return;
+ },
+ function (mixed $value, Document $document, Database $database) {
+ return $database
+ ->find('variables', [
+ Query::equal('resourceInternalId', [$document->getInternalId()]),
+ Query::equal('resourceType', ['function']),
+ Query::limit(APP_LIMIT_SUBQUERY),
+ ]);
+ }
+);
+
+Database::addFilter(
+ 'encrypt',
+ function (mixed $value) {
+ $key = System::getEnv('_APP_OPENSSL_KEY_V1');
+ $iv = OpenSSL::randomPseudoBytes(OpenSSL::cipherIVLength(OpenSSL::CIPHER_AES_128_GCM));
+ $tag = null;
+
+ return json_encode([
+ 'data' => OpenSSL::encrypt($value, OpenSSL::CIPHER_AES_128_GCM, $key, 0, $iv, $tag),
+ 'method' => OpenSSL::CIPHER_AES_128_GCM,
+ 'iv' => \bin2hex($iv),
+ 'tag' => \bin2hex($tag ?? ''),
+ 'version' => '1',
+ ]);
+ },
+ function (mixed $value) {
+ if (is_null($value)) {
+ return;
+ }
+ $value = json_decode($value, true);
+ $key = System::getEnv('_APP_OPENSSL_KEY_V' . $value['version']);
+
+ return OpenSSL::decrypt($value['data'], $value['method'], $key, 0, hex2bin($value['iv']), hex2bin($value['tag']));
+ }
+);
+
+Database::addFilter(
+ 'subQueryProjectVariables',
+ function (mixed $value) {
+ return;
+ },
+ function (mixed $value, Document $document, Database $database) {
+ return $database
+ ->find('variables', [
+ Query::equal('resourceType', ['project']),
+ Query::limit(APP_LIMIT_SUBQUERY)
+ ]);
+ }
+);
+
+Database::addFilter(
+ 'userSearch',
+ function (mixed $value, Document $user) {
+ $searchValues = [
+ $user->getId(),
+ $user->getAttribute('email', ''),
+ $user->getAttribute('name', ''),
+ $user->getAttribute('phone', '')
+ ];
+
+ foreach ($user->getAttribute('labels', []) as $label) {
+ $searchValues[] = 'label:' . $label;
+ }
+
+ $search = implode(' ', \array_filter($searchValues));
+
+ return $search;
+ },
+ function (mixed $value) {
+ return $value;
+ }
+);
+
+Database::addFilter(
+ 'subQueryTargets',
+ function (mixed $value) {
+ return;
+ },
+ function (mixed $value, Document $document, Database $database) {
+ return Authorization::skip(fn () => $database
+ ->find('targets', [
+ Query::equal('userInternalId', [$document->getInternalId()]),
+ Query::limit(APP_LIMIT_SUBQUERY)
+ ]));
+ }
+);
+
+Database::addFilter(
+ 'subQueryTopicTargets',
+ function (mixed $value) {
+ return;
+ },
+ function (mixed $value, Document $document, Database $database) {
+ $targetIds = Authorization::skip(fn () => \array_map(
+ fn ($document) => $document->getAttribute('targetInternalId'),
+ $database->find('subscribers', [
+ Query::equal('topicInternalId', [$document->getInternalId()]),
+ Query::limit(APP_LIMIT_SUBSCRIBERS_SUBQUERY)
+ ])
+ ));
+ if (\count($targetIds) > 0) {
+ return $database->skipValidation(fn () => $database->find('targets', [
+ Query::equal('$internalId', $targetIds)
+ ]));
+ }
+ return [];
+ }
+);
+
+Database::addFilter(
+ 'providerSearch',
+ function (mixed $value, Document $provider) {
+ $searchValues = [
+ $provider->getId(),
+ $provider->getAttribute('name', ''),
+ $provider->getAttribute('provider', ''),
+ $provider->getAttribute('type', '')
+ ];
+
+ $search = \implode(' ', \array_filter($searchValues));
+
+ return $search;
+ },
+ function (mixed $value) {
+ return $value;
+ }
+);
+
+Database::addFilter(
+ 'topicSearch',
+ function (mixed $value, Document $topic) {
+ $searchValues = [
+ $topic->getId(),
+ $topic->getAttribute('name', ''),
+ $topic->getAttribute('description', ''),
+ ];
+
+ $search = \implode(' ', \array_filter($searchValues));
+
+ return $search;
+ },
+ function (mixed $value) {
+ return $value;
+ }
+);
+
+Database::addFilter(
+ 'messageSearch',
+ function (mixed $value, Document $message) {
+ $searchValues = [
+ $message->getId(),
+ $message->getAttribute('description', ''),
+ $message->getAttribute('status', ''),
+ ];
+
+ $data = \json_decode($message->getAttribute('data', []), true);
+ $providerType = $message->getAttribute('providerType', '');
+
+ if ($providerType === MESSAGE_TYPE_EMAIL) {
+ $searchValues = \array_merge($searchValues, [$data['subject'], MESSAGE_TYPE_EMAIL]);
+ } elseif ($providerType === MESSAGE_TYPE_SMS) {
+ $searchValues = \array_merge($searchValues, [$data['content'], MESSAGE_TYPE_SMS]);
+ } else {
+ $searchValues = \array_merge($searchValues, [$data['title'], MESSAGE_TYPE_PUSH]);
+ }
+
+ $search = \implode(' ', \array_filter($searchValues));
+
+ return $search;
+ },
+ function (mixed $value) {
+ return $value;
+ }
+);
+
+/**
+ * DB Formats
+ */
+Structure::addFormat(APP_DATABASE_ATTRIBUTE_EMAIL, function () {
+ return new Email();
+}, Database::VAR_STRING);
+
+Structure::addFormat(APP_DATABASE_ATTRIBUTE_DATETIME, function () {
+ return new DatetimeValidator();
+}, Database::VAR_DATETIME);
+
+Structure::addFormat(APP_DATABASE_ATTRIBUTE_ENUM, function ($attribute) {
+ $elements = $attribute['formatOptions']['elements'];
+ return new WhiteList($elements, true);
+}, Database::VAR_STRING);
+
+Structure::addFormat(APP_DATABASE_ATTRIBUTE_IP, function () {
+ return new IP();
+}, Database::VAR_STRING);
+
+Structure::addFormat(APP_DATABASE_ATTRIBUTE_URL, function () {
+ return new URL();
+}, Database::VAR_STRING);
+
+Structure::addFormat(APP_DATABASE_ATTRIBUTE_INT_RANGE, function ($attribute) {
+ $min = $attribute['formatOptions']['min'] ?? -INF;
+ $max = $attribute['formatOptions']['max'] ?? INF;
+ return new Range($min, $max, Range::TYPE_INTEGER);
+}, Database::VAR_INTEGER);
+
+Structure::addFormat(APP_DATABASE_ATTRIBUTE_FLOAT_RANGE, function ($attribute) {
+ $min = $attribute['formatOptions']['min'] ?? -INF;
+ $max = $attribute['formatOptions']['max'] ?? INF;
+ return new Range($min, $max, Range::TYPE_FLOAT);
+}, Database::VAR_FLOAT);
+
+/*
+ * Registry
+ */
+$register->set('logger', function () {
// Register error logger
$providerName = System::getEnv('_APP_LOGGING_PROVIDER', '');
$providerConfig = System::getEnv('_APP_LOGGING_CONFIG', '');
@@ -202,12 +777,12 @@ $registry->set('logger', function () {
default => ['key' => $loggingProvider->getHost()],
};
} catch (Throwable $th) {
- Console::warning('Using deprecated logging configuration. Please update your configuration to use DSN format.' . $th->getMessage());
// Fallback for older Appwrite versions up to 1.5.x that use _APP_LOGGING_PROVIDER and _APP_LOGGING_CONFIG environment variables
+ Console::warning('Using deprecated logging configuration. Please update your configuration to use DSN format.' . $th->getMessage());
$configChunks = \explode(";", $providerConfig);
$providerConfig = match ($providerName) {
- 'sentry' => ['key' => $configChunks[0], 'projectId' => $configChunks[1] ?? '', 'host' => '',],
+ 'sentry' => [ 'key' => $configChunks[0], 'projectId' => $configChunks[1] ?? '', 'host' => '',],
'logowl' => ['ticket' => $configChunks[0] ?? '', 'host' => ''],
default => ['key' => $providerConfig],
};
@@ -238,167 +813,200 @@ $registry->set('logger', function () {
return;
}
- $logger = new Logger($adapter);
- $logger->setSample(0.4);
- return $logger;
+ return new Logger($adapter);
});
-$registry->set('geodb', function () {
- /**
- * @disregard P1009 Undefined type
- */
- return new Reader(__DIR__ . '/assets/dbip/dbip-country-lite-2024-09.mmdb');
-});
+$register->set('pools', function () {
+ $group = new Group();
-$registry->set('hooks', function () {
- return new Hooks();
-});
+ $fallbackForDB = 'db_main=' . AppwriteURL::unparse([
+ 'scheme' => 'mariadb',
+ 'host' => System::getEnv('_APP_DB_HOST', 'mariadb'),
+ 'port' => System::getEnv('_APP_DB_PORT', '3306'),
+ 'user' => System::getEnv('_APP_DB_USER', ''),
+ 'pass' => System::getEnv('_APP_DB_PASS', ''),
+ 'path' => System::getEnv('_APP_DB_SCHEMA', ''),
+ ]);
+ $fallbackForRedis = 'redis_main=' . AppwriteURL::unparse([
+ 'scheme' => 'redis',
+ 'host' => System::getEnv('_APP_REDIS_HOST', 'redis'),
+ 'port' => System::getEnv('_APP_REDIS_PORT', '6379'),
+ 'user' => System::getEnv('_APP_REDIS_USER', ''),
+ 'pass' => System::getEnv('_APP_REDIS_PASS', ''),
+ ]);
-$registry->set(
- 'pools',
- (function () {
- $fallbackForDB = 'db_main=' . URL::unparse([
- 'scheme' => 'mariadb',
- 'host' => System::getEnv('_APP_DB_HOST', 'mariadb'),
- 'port' => System::getEnv('_APP_DB_PORT', '3306'),
- 'user' => System::getEnv('_APP_DB_USER', ''),
- 'pass' => System::getEnv('_APP_DB_PASS', ''),
- 'path' => System::getEnv('_APP_DB_SCHEMA', ''),
- ]);
- $fallbackForRedis = 'redis_main=' . URL::unparse([
- 'scheme' => 'redis',
- 'host' => System::getEnv('_APP_REDIS_HOST', 'redis'),
- 'port' => System::getEnv('_APP_REDIS_PORT', '6379'),
- 'user' => System::getEnv('_APP_REDIS_USER', ''),
- 'pass' => System::getEnv('_APP_REDIS_PASS', ''),
- ]);
+ $connections = [
+ 'console' => [
+ 'type' => 'database',
+ 'dsns' => System::getEnv('_APP_CONNECTIONS_DB_CONSOLE', $fallbackForDB),
+ 'multiple' => false,
+ 'schemes' => ['mariadb', 'mysql'],
+ ],
+ 'database' => [
+ 'type' => 'database',
+ 'dsns' => System::getEnv('_APP_CONNECTIONS_DB_PROJECT', $fallbackForDB),
+ 'multiple' => true,
+ 'schemes' => ['mariadb', 'mysql'],
+ ],
+ 'queue' => [
+ 'type' => 'queue',
+ 'dsns' => System::getEnv('_APP_CONNECTIONS_QUEUE', $fallbackForRedis),
+ 'multiple' => false,
+ 'schemes' => ['redis'],
+ ],
+ 'pubsub' => [
+ 'type' => 'pubsub',
+ 'dsns' => System::getEnv('_APP_CONNECTIONS_PUBSUB', $fallbackForRedis),
+ 'multiple' => false,
+ 'schemes' => ['redis'],
+ ],
+ 'cache' => [
+ 'type' => 'cache',
+ 'dsns' => System::getEnv('_APP_CONNECTIONS_CACHE', $fallbackForRedis),
+ 'multiple' => true,
+ 'schemes' => ['redis'],
+ ],
+ ];
- $connections = [
- 'console' => [
- 'type' => 'database',
- 'dsns' => System::getEnv('_APP_CONNECTIONS_DB_CONSOLE', $fallbackForDB),
- 'multiple' => false,
- 'schemes' => ['mariadb', 'mysql'],
- ],
- 'database' => [
- 'type' => 'database',
- 'dsns' => System::getEnv('_APP_CONNECTIONS_DB_PROJECT', $fallbackForDB),
- 'multiple' => true,
- 'schemes' => ['mariadb', 'mysql'],
- ],
- 'queue' => [
- 'type' => 'queue',
- 'dsns' => System::getEnv('_APP_CONNECTIONS_QUEUE', $fallbackForRedis),
- 'multiple' => false,
- 'schemes' => ['redis'],
- ],
- 'pubsub' => [
- 'type' => 'pubsub',
- 'dsns' => System::getEnv('_APP_CONNECTIONS_PUBSUB', $fallbackForRedis),
- 'multiple' => false,
- 'schemes' => ['redis'],
- ],
- 'cache' => [
- 'type' => 'cache',
- 'dsns' => System::getEnv('_APP_CONNECTIONS_CACHE', $fallbackForRedis),
- 'multiple' => true,
- 'schemes' => ['redis'],
- ],
- ];
+ $maxConnections = System::getEnv('_APP_CONNECTIONS_MAX', 151);
+ $instanceConnections = $maxConnections / System::getEnv('_APP_POOL_CLIENTS', 14);
- $pools = [];
- $poolSize = (int)System::getEnv('_APP_POOL_SIZE', 64);
+ $multiprocessing = System::getEnv('_APP_SERVER_MULTIPROCESS', 'disabled') === 'enabled';
- foreach ($connections as $key => $connection) {
- $dsns = $connection['dsns'] ?? '';
- $multiple = $connection['multiple'] ?? false;
- $schemes = $connection['schemes'] ?? [];
- $dsns = explode(',', $connection['dsns'] ?? '');
- $config = [];
+ if ($multiprocessing) {
+ $workerCount = swoole_cpu_num() * intval(System::getEnv('_APP_WORKER_PER_CORE', 6));
+ } else {
+ $workerCount = 1;
+ }
- foreach ($dsns as &$dsn) {
- $dsn = explode('=', $dsn);
- $name = ($multiple) ? $key . '_' . $dsn[0] : $key;
- $config[] = $name;
- $dsn = $dsn[1] ?? '';
+ if ($workerCount > $instanceConnections) {
+ throw new \Exception('Pool size is too small. Increase the number of allowed database connections or decrease the number of workers.', 500);
+ }
- if (empty($dsn)) {
- throw new Exception(Exception::GENERAL_SERVER_ERROR, "Missing value for DSN connection in {$key}");
- }
+ $poolSize = (int)($instanceConnections / $workerCount);
- $dsn = new DSN($dsn);
- $dsnHost = $dsn->getHost();
- $dsnPort = $dsn->getPort();
- $dsnUser = $dsn->getUser();
- $dsnPass = $dsn->getPassword();
- $dsnScheme = $dsn->getScheme();
- $dsnDatabase = $dsn->getPath();
+ foreach ($connections as $key => $connection) {
+ $type = $connection['type'] ?? '';
+ $multiple = $connection['multiple'] ?? false;
+ $schemes = $connection['schemes'] ?? [];
+ $config = [];
+ $dsns = explode(',', $connection['dsns'] ?? '');
+ foreach ($dsns as &$dsn) {
+ $dsn = explode('=', $dsn);
+ $name = ($multiple) ? $key . '_' . $dsn[0] : $key;
+ $dsn = $dsn[1] ?? '';
+ $config[] = $name;
+ if (empty($dsn)) {
+ //throw new Exception(Exception::GENERAL_SERVER_ERROR, "Missing value for DSN connection in {$key}");
+ continue;
+ }
- if (!in_array($dsnScheme, $schemes)) {
- throw new Exception(Exception::GENERAL_SERVER_ERROR, "Invalid console database scheme");
- }
+ $dsn = new DSN($dsn);
+ $dsnHost = $dsn->getHost();
+ $dsnPort = $dsn->getPort();
+ $dsnUser = $dsn->getUser();
+ $dsnPass = $dsn->getPassword();
+ $dsnScheme = $dsn->getScheme();
+ $dsnDatabase = $dsn->getPath();
- /**
- * Get Resource
- *
- * Creation could be reused accross connection types like database, cache, queue, etc.
- *
- * Resource assignment to an adapter will happen below.
- */
- switch ($dsnScheme) {
- case 'mysql':
- case 'mariadb':
- $pool = new PDOPool(
- (new PDOConfig())
- ->withHost($dsnHost)
- ->withPort($dsnPort)
- ->withDbName($dsnDatabase)
- ->withCharset('utf8mb4')
- ->withUsername($dsnUser)
- ->withPassword($dsnPass)
- ->withOptions([
- // No need to set PDO::ATTR_ERRMODE it is overwitten in PDOProxy
- // PDO::ATTR_TIMEOUT => 3, // Seconds
- // PDO::ATTR_PERSISTENT => true,
- PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
- PDO::ATTR_EMULATE_PREPARES => true,
- PDO::ATTR_STRINGIFY_FETCHES => true,
- PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true,
+ if (!in_array($dsnScheme, $schemes)) {
+ throw new Exception(Exception::GENERAL_SERVER_ERROR, "Invalid console database scheme");
+ }
- ]),
- $poolSize
- );
+ /**
+ * Get Resource
+ *
+ * Creation could be reused across connection types like database, cache, queue, etc.
+ *
+ * Resource assignment to an adapter will happen below.
+ */
+ $resource = match ($dsnScheme) {
+ 'mysql',
+ 'mariadb' => function () use ($dsnHost, $dsnPort, $dsnUser, $dsnPass, $dsnDatabase) {
+ return new PDOProxy(function () use ($dsnHost, $dsnPort, $dsnUser, $dsnPass, $dsnDatabase) {
+ return new PDO("mysql:host={$dsnHost};port={$dsnPort};dbname={$dsnDatabase};charset=utf8mb4", $dsnUser, $dsnPass, array(
+ PDO::ATTR_TIMEOUT => 3, // Seconds
+ PDO::ATTR_PERSISTENT => true,
+ PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
+ PDO::ATTR_EMULATE_PREPARES => true,
+ PDO::ATTR_STRINGIFY_FETCHES => true
+ ));
+ });
+ },
+ 'redis' => function () use ($dsnHost, $dsnPort, $dsnPass) {
+ $redis = new Redis();
+ @$redis->pconnect($dsnHost, (int)$dsnPort);
+ if ($dsnPass) {
+ $redis->auth($dsnPass);
+ }
+ $redis->setOption(Redis::OPT_READ_TIMEOUT, -1);
+
+ return $redis;
+ },
+ default => throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Invalid scheme'),
+ };
+
+ $pool = new Pool($name, $poolSize, function () use ($type, $resource, $dsn) {
+ // Get Adapter
+ switch ($type) {
+ case 'database':
+ $adapter = match ($dsn->getScheme()) {
+ 'mariadb' => new MariaDB($resource()),
+ 'mysql' => new MySQL($resource()),
+ default => null
+ };
+
+ $adapter->setDatabase($dsn->getPath());
break;
- case 'redis':
- $pool = new RedisPool(
- (new RedisConfig())
- ->withHost($dsnHost)
- ->withPort((int)$dsnPort)
- ->withAuth($dsnPass ?? ''),
- $poolSize
- );
+ case 'pubsub':
+ $adapter = $resource();
+ break;
+ case 'queue':
+ $adapter = match ($dsn->getScheme()) {
+ 'redis' => new Queue\Connection\Redis($dsn->getHost(), $dsn->getPort()),
+ default => null
+ };
+ break;
+ case 'cache':
+ $adapter = match ($dsn->getScheme()) {
+ 'redis' => new RedisCache($resource()),
+ default => null
+ };
break;
default:
- throw new Exception(Exception::GENERAL_SERVER_ERROR, "Invalid scheme");
+ throw new Exception(Exception::GENERAL_SERVER_ERROR, "Server error: Missing adapter implementation.");
}
- $pools['pools-' . $key . '-' . $name] = [
- 'pool' => $pool,
- 'dsn' => $dsn,
- ];
- }
+ return $adapter;
+ });
- Config::setParam('pools-' . $key, $config);
+ $group->add($pool);
}
- return function () use ($pools): array {
- return $pools;
- };
- })()
-);
+ Config::setParam('pools-' . $key, $config);
+ }
-$registry->set('smtp', function () {
+ return $group;
+});
+
+$register->set('db', function () {
+ // This is usually for our workers or CLI commands scope
+ $dbHost = System::getEnv('_APP_DB_HOST', '');
+ $dbPort = System::getEnv('_APP_DB_PORT', '');
+ $dbUser = System::getEnv('_APP_DB_USER', '');
+ $dbPass = System::getEnv('_APP_DB_PASS', '');
+ $dbScheme = System::getEnv('_APP_DB_SCHEMA', '');
+
+ return new PDO(
+ "mysql:host={$dbHost};port={$dbPort};dbname={$dbScheme};charset=utf8mb4",
+ $dbUser,
+ $dbPass,
+ SQL::getPDOAttributes()
+ );
+});
+
+$register->set('smtp', function () {
$mail = new PHPMailer(true);
$mail->isSMTP();
@@ -426,350 +1034,415 @@ $registry->set('smtp', function () {
return $mail;
});
-
-$registry->set('promiseAdapter', function () {
+$register->set('geodb', function () {
+ return new Reader(__DIR__ . '/assets/dbip/dbip-country-lite-2024-09.mmdb');
+});
+$register->set('passwordsDictionary', function () {
+ $content = \file_get_contents(__DIR__ . '/assets/security/10k-common-passwords');
+ $content = explode("\n", $content);
+ $content = array_flip($content);
+ return $content;
+});
+$register->set('promiseAdapter', function () {
return new Swoole();
});
+$register->set('hooks', function () {
+ return new Hooks();
+});
+/*
+ * Localization
+ */
+Locale::$exceptions = false;
-$registry->set('db', function () {
- // This is usually for our workers or CLI commands scope
- $dbHost = System::getEnv('_APP_DB_HOST', '');
- $dbPort = System::getEnv('_APP_DB_PORT', '');
- $dbUser = System::getEnv('_APP_DB_USER', '');
- $dbPass = System::getEnv('_APP_DB_PASS', '');
- $dbScheme = System::getEnv('_APP_DB_SCHEMA', '');
+$locales = Config::getParam('locale-codes', []);
- return new PDO(
- "mysql:host={$dbHost};port={$dbPort};dbname={$dbScheme};charset=utf8mb4",
- $dbUser,
- $dbPass,
- SQL::getPDOAttributes()
- );
+foreach ($locales as $locale) {
+ $code = $locale['code'];
+
+ $path = __DIR__ . '/config/locale/translations/' . $code . '.json';
+
+ if (!\file_exists($path)) {
+ $path = __DIR__ . '/config/locale/translations/' . \substr($code, 0, 2) . '.json'; // if `ar-ae` doesn't exist, look for `ar`
+ if (!\file_exists($path)) {
+ $path = __DIR__ . '/config/locale/translations/en.json'; // if none translation exists, use default from `en.json`
+ }
+ }
+
+ Locale::setLanguageFromJSON($code, $path);
+}
+
+\stream_context_set_default([ // Set global user agent and http settings
+ 'http' => [
+ 'method' => 'GET',
+ 'user_agent' => \sprintf(
+ APP_USERAGENT,
+ System::getEnv('_APP_VERSION', 'UNKNOWN'),
+ System::getEnv('_APP_EMAIL_SECURITY', System::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS', APP_EMAIL_SECURITY))
+ ),
+ 'timeout' => 2,
+ ],
+]);
+
+// Runtime Execution
+App::setResource('log', fn () => new Log());
+App::setResource('logger', function ($register) {
+ return $register->get('logger');
+}, ['register']);
+
+App::setResource('hooks', function ($register) {
+ return $register->get('hooks');
+}, ['register']);
+
+App::setResource('register', fn () => $register);
+App::setResource('locale', fn () => new Locale(System::getEnv('_APP_LOCALE', 'en')));
+
+App::setResource('localeCodes', function () {
+ return array_map(fn ($locale) => $locale['code'], Config::getParam('locale-codes', []));
});
-// Autoload
-class_exists(JWT::class, true);
-class_exists(DSN::class, true);
-class_exists(Log::class, true);
-class_exists(TOTP::class, true);
-class_exists(Mail::class, true);
-class_exists(Func::class, true);
-class_exists(Cache::class, true);
-class_exists(Abuse::class, true);
-class_exists(MySQL::class, true);
-class_exists(Event::class, true);
-class_exists(Audit::class, true);
-class_exists(Usage::class, true);
-class_exists(Local::class, true);
-class_exists(Build::class, true);
-class_exists(Locale::class, true);
-class_exists(Delete::class, true);
-class_exists(GitHub::class, true);
-class_exists(Schema::class, true);
-class_exists(Domain::class, true);
-class_exists(Console::class, true);
-class_exists(Request::class, true);
-class_exists(MariaDB::class, true);
-class_exists(Document::class, true);
-class_exists(Sharding::class, true);
-class_exists(Database::class, true);
-class_exists(Hostname::class, true);
-class_exists(TimeLimit::class, true);
-class_exists(Migration::class, true);
-class_exists(Messaging::class, true);
-class_exists(CacheRedis::class, true);
-class_exists(Connections::class, true);
-class_exists(Certificate::class, true);
-class_exists(EventDatabase::class, true);
-class_exists(Authorization::class, true);
-class_exists(Authentication::class, true);
-class_exists(Queue\Connection\Redis::class, true);
+// Queues
+App::setResource('queue', function (Group $pools) {
+ return $pools->get('queue')->pop()->getResource();
+}, ['pools']);
+App::setResource('queueForMessaging', function (Connection $queue) {
+ return new Messaging($queue);
+}, ['queue']);
+App::setResource('queueForMails', function (Connection $queue) {
+ return new Mail($queue);
+}, ['queue']);
+App::setResource('queueForBuilds', function (Connection $queue) {
+ return new Build($queue);
+}, ['queue']);
+App::setResource('queueForDatabase', function (Connection $queue) {
+ return new EventDatabase($queue);
+}, ['queue']);
+App::setResource('queueForDeletes', function (Connection $queue) {
+ return new Delete($queue);
+}, ['queue']);
+App::setResource('queueForEvents', function (Connection $queue) {
+ return new Event($queue);
+}, ['queue']);
+App::setResource('queueForAudits', function (Connection $queue) {
+ return new Audit($queue);
+}, ['queue']);
+App::setResource('queueForFunctions', function (Connection $queue) {
+ return new Func($queue);
+}, ['queue']);
+App::setResource('queueForUsage', function (Connection $queue) {
+ return new Usage($queue);
+}, ['queue']);
+App::setResource('queueForCertificates', function (Connection $queue) {
+ return new Certificate($queue);
+}, ['queue']);
+App::setResource('queueForMigrations', function (Connection $queue) {
+ return new Migration($queue);
+}, ['queue']);
+App::setResource('clients', function ($request, $console, $project) {
+ $console->setAttribute('platforms', [ // Always allow current host
+ '$collection' => ID::custom('platforms'),
+ 'name' => 'Current Host',
+ 'type' => Origin::CLIENT_TYPE_WEB,
+ 'hostname' => $request->getHostname(),
+ ], Document::SET_TYPE_APPEND);
-$log = new Dependency();
-$mode = new Dependency();
-$user = new Dependency();
-$plan = new Dependency();
-$pools = new Dependency();
-$geodb = new Dependency();
-$cache = new Dependency();
-$pools = new Dependency();
-$queue = new Dependency();
-$hooks = new Dependency();
-$logger = new Dependency();
-$locale = new Dependency();
-$schema = new Dependency();
-$github = new Dependency();
-$session = new Dependency();
-$console = new Dependency();
-$project = new Dependency();
-$clients = new Dependency();
-$servers = new Dependency();
-$register = new Dependency();
-$connections = new Dependency();
-$localeCodes = new Dependency();
-$getConsoleDB = new Dependency();
-$getProjectDB = new Dependency();
-$dbForProject = new Dependency();
-$dbForConsole = new Dependency();
-$queueForUsage = new Dependency();
-$queueForMails = new Dependency();
-$authorization = new Dependency();
-$authentication = new Dependency();
-$queueForBuilds = new Dependency();
-$deviceForLocal = new Dependency();
-$deviceForFiles = new Dependency();
-$queueForEvents = new Dependency();
-$queueForAudits = new Dependency();
-$promiseAdapter = new Dependency();
-$schemaVariable = new Dependency();
-$deviceForBuilds = new Dependency();
-$queueForDeletes = new Dependency();
-$requestTimestamp = new Dependency();
-$queueForDatabase = new Dependency();
-$queueForMessaging = new Dependency();
-$queueForFunctions = new Dependency();
-$queueForMigrations = new Dependency();
-$deviceForFunctions = new Dependency();
-$passwordsDictionary = new Dependency();
-$queueForCertificates = new Dependency();
-
-
-$plan
- ->setName('plan')
- ->setCallback(fn () => []);
-
-$mode
- ->setName('mode')
- ->inject('request')
- ->setCallback(function (Request $request) {
- /**
- * Defines the mode for the request:
- * - 'default' => Requests for Client and Server Side
- * - 'admin' => Request from the Console on non-console projects
- */
- return $request->getParam('mode', $request->getHeader('x-appwrite-mode', APP_MODE_DEFAULT));
- });
-
-$user
- ->setName('user')
- ->inject('mode')
- ->inject('project')
- ->inject('console')
- ->inject('request')
- ->inject('response')
- ->inject('dbForProject')
- ->inject('dbForConsole')
- ->inject('authorization')
- ->inject('authentication')
- ->setCallback(function (string $mode, Document $project, Document $console, Request $request, Response $response, Database $dbForProject, Database $dbForConsole, Authorization $authorization, Authentication $authentication) {
- $authorization->setDefaultStatus(true);
- $authentication->setCookieName('a_session_' . $project->getId());
-
- if (APP_MODE_ADMIN === $mode) {
- $authentication->setCookieName('a_session_' . $console->getId());
+ $hostnames = explode(',', System::getEnv('_APP_CONSOLE_HOSTNAMES', ''));
+ $validator = new Hostname();
+ foreach ($hostnames as $hostname) {
+ $hostname = trim($hostname);
+ if (!$validator->isValid($hostname)) {
+ continue;
}
+ $console->setAttribute('platforms', [
+ '$collection' => ID::custom('platforms'),
+ 'type' => Origin::CLIENT_TYPE_WEB,
+ 'name' => $hostname,
+ 'hostname' => $hostname,
+ ], Document::SET_TYPE_APPEND);
+ }
- $session = Auth::decodeSession(
- $request->getCookie(
- $authentication->getCookieName(), // Get sessions
- $request->getCookie($authentication->getCookieName() . '_legacy', '')
- )
- );
+ /**
+ * Get All verified client URLs for both console and current projects
+ * + Filter for duplicated entries
+ */
+ $clientsConsole = \array_map(
+ fn ($node) => $node['hostname'],
+ \array_filter(
+ $console->getAttribute('platforms', []),
+ fn ($node) => (isset($node['type']) && ($node['type'] === Origin::CLIENT_TYPE_WEB) && !empty($node['hostname']))
+ )
+ );
- // Get session from header for SSR clients
- if (empty($session['id']) && empty($session['secret'])) {
- $sessionHeader = $request->getHeader('x-appwrite-session', '');
-
- if (!empty($sessionHeader)) {
- $session = Auth::decodeSession($sessionHeader);
- }
- }
-
- // Get fallback session from old clients (no SameSite support) or clients who block 3rd-party cookies
- if ($response) {
- $response->addHeader('X-Debug-Fallback', 'false');
- }
-
- if (empty($session['id']) && empty($session['secret'])) {
- if ($response) {
- $response->addHeader('X-Debug-Fallback', 'true');
- }
- $fallback = $request->getHeader('x-fallback-cookies', '');
- $fallback = \json_decode($fallback, true);
- $session = Auth::decodeSession(((isset($fallback[$authentication->getCookieName()])) ? $fallback[$authentication->getCookieName()] : ''));
- }
-
- $authentication->setUnique($session['id'] ?? '');
- $authentication->setSecret($session['secret'] ?? '');
-
- if (APP_MODE_ADMIN !== $mode) {
- if ($project->isEmpty()) {
- $user = new Document([]);
- } else {
- if ($project->getId() === 'console') {
- $user = $dbForConsole->getDocument('users', $authentication->getUnique());
- } else {
- $user = $dbForProject->getDocument('users', $authentication->getUnique());
- }
- }
- } else {
- $user = $dbForConsole->getDocument('users', $authentication->getUnique());
- }
+ $clients = $clientsConsole;
+ $platforms = $project->getAttribute('platforms', []);
+ foreach ($platforms as $node) {
if (
- $user->isEmpty() // Check a document has been found in the DB
- || !Auth::sessionVerify($user->getAttribute('sessions', []), $authentication->getSecret())
- ) { // Validate user has valid login token
+ isset($node['type']) &&
+ ($node['type'] === Origin::CLIENT_TYPE_WEB ||
+ $node['type'] === Origin::CLIENT_TYPE_FLUTTER_WEB) &&
+ !empty($node['hostname'])
+ ) {
+ $clients[] = $node['hostname'];
+ }
+ }
+
+ return \array_unique($clients);
+}, ['request', 'console', 'project']);
+
+App::setResource('user', function ($mode, $project, $console, $request, $response, $dbForProject, $dbForConsole) {
+ /** @var Appwrite\Utopia\Request $request */
+ /** @var Appwrite\Utopia\Response $response */
+ /** @var Utopia\Database\Document $project */
+ /** @var Utopia\Database\Database $dbForProject */
+ /** @var Utopia\Database\Database $dbForConsole */
+ /** @var string $mode */
+
+ Authorization::setDefaultStatus(true);
+
+ Auth::setCookieName('a_session_' . $project->getId());
+
+ if (APP_MODE_ADMIN === $mode) {
+ Auth::setCookieName('a_session_' . $console->getId());
+ }
+
+ $session = Auth::decodeSession(
+ $request->getCookie(
+ Auth::$cookieName, // Get sessions
+ $request->getCookie(Auth::$cookieName . '_legacy', '')
+ )
+ );
+
+ // Get session from header for SSR clients
+ if (empty($session['id']) && empty($session['secret'])) {
+ $sessionHeader = $request->getHeader('x-appwrite-session', '');
+
+ if (!empty($sessionHeader)) {
+ $session = Auth::decodeSession($sessionHeader);
+ }
+ }
+
+ // Get fallback session from old clients (no SameSite support) or clients who block 3rd-party cookies
+ if ($response) {
+ $response->addHeader('X-Debug-Fallback', 'false');
+ }
+
+ if (empty($session['id']) && empty($session['secret'])) {
+ if ($response) {
+ $response->addHeader('X-Debug-Fallback', 'true');
+ }
+ $fallback = $request->getHeader('x-fallback-cookies', '');
+ $fallback = \json_decode($fallback, true);
+ $session = Auth::decodeSession(((isset($fallback[Auth::$cookieName])) ? $fallback[Auth::$cookieName] : ''));
+ }
+
+ Auth::$unique = $session['id'] ?? '';
+ Auth::$secret = $session['secret'] ?? '';
+
+ if (APP_MODE_ADMIN !== $mode) {
+ if ($project->isEmpty()) {
$user = new Document([]);
+ } else {
+ if ($project->getId() === 'console') {
+ $user = $dbForConsole->getDocument('users', Auth::$unique);
+ } else {
+ $user = $dbForProject->getDocument('users', Auth::$unique);
+ }
+ }
+ } else {
+ $user = $dbForConsole->getDocument('users', Auth::$unique);
+ }
+
+ if (
+ $user->isEmpty() // Check a document has been found in the DB
+ || !Auth::sessionVerify($user->getAttribute('sessions', []), Auth::$secret)
+ ) { // Validate user has valid login token
+ $user = new Document([]);
+ }
+
+ // if (APP_MODE_ADMIN === $mode) {
+ // if ($user->find('teamInternalId', $project->getAttribute('teamInternalId'), 'memberships')) {
+ // Authorization::setDefaultStatus(false); // Cancel security segmentation for admin users.
+ // } else {
+ // $user = new Document([]);
+ // }
+ // }
+
+ $authJWT = $request->getHeader('x-appwrite-jwt', '');
+
+ if (!empty($authJWT) && !$project->isEmpty()) { // JWT authentication
+ $jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 3600, 0);
+
+ try {
+ $payload = $jwt->decode($authJWT);
+ } catch (JWTException $error) {
+ throw new Exception(Exception::USER_JWT_INVALID, 'Failed to verify JWT. ' . $error->getMessage());
}
- if (APP_MODE_ADMIN === $mode) {
- if ($user->find('teamId', $project->getAttribute('teamId'), 'memberships')) {
- $authorization->setDefaultStatus(false); // Cancel security segmentation for admin users.
- } else {
+ $jwtUserId = $payload['userId'] ?? '';
+ if (!empty($jwtUserId)) {
+ $user = $dbForProject->getDocument('users', $jwtUserId);
+ }
+
+ $jwtSessionId = $payload['sessionId'] ?? '';
+ if (!empty($jwtSessionId)) {
+ if (empty($user->find('$id', $jwtSessionId, 'sessions'))) { // Match JWT to active token
$user = new Document([]);
}
}
+ }
- $authJWT = $request->getHeader('x-appwrite-jwt', '');
+ $dbForProject->setMetadata('user', $user->getId());
+ $dbForConsole->setMetadata('user', $user->getId());
- if (!empty($authJWT) && !$project->isEmpty()) { // JWT authentication
- $jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 3600, 0);
- try {
- $payload = $jwt->decode($authJWT);
- } catch (JWTException $error) {
- $request->removeHeader('x-appwrite-jwt');
- throw new Exception(Exception::USER_JWT_INVALID, 'Failed to verify JWT. ' . $error->getMessage());
- }
+ return $user;
+}, ['mode', 'project', 'console', 'request', 'response', 'dbForProject', 'dbForConsole']);
- $jwtUserId = $payload['userId'] ?? '';
- if (!empty($jwtUserId)) {
- $user = $dbForProject->getDocument('users', $jwtUserId);
- }
+App::setResource('project', function ($dbForConsole, $request, $console) {
+ /** @var Appwrite\Utopia\Request $request */
+ /** @var Utopia\Database\Database $dbForConsole */
+ /** @var Utopia\Database\Document $console */
- $jwtSessionId = $payload['sessionId'] ?? '';
- if (!empty($jwtSessionId)) {
- if (empty($user->find('$id', $jwtSessionId, 'sessions'))) { // Match JWT to active token
- $user = new Document([]);
- }
- }
- }
+ $projectId = $request->getParam('project', $request->getHeader('x-appwrite-project', ''));
- // Adds logs to database queries
- $dbForProject->setMetadata('user', $user->getId());
- $dbForConsole->setMetadata('user', $user->getId());
+ if (empty($projectId) || $projectId === 'console') {
+ return $console;
+ }
- return $user;
- });
+ $project = Authorization::skip(fn () => $dbForConsole->getDocument('projects', $projectId));
+ return $project;
+}, ['dbForConsole', 'request', 'console']);
-$session
- ->setName('session')
- ->inject('user')
- ->inject('project')
- ->inject('authorization')
- ->inject('authentication')
- ->setCallback(function (Document $user, Document $project, Authorization $authorization, Authentication $authentication) {
- if ($user->isEmpty()) {
- return;
- }
-
- $sessions = $user->getAttribute('sessions', []);
- $authDuration = $project->getAttribute('auths', [])['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG;
- $sessionId = Auth::sessionVerify($user->getAttribute('sessions'), $authentication->getSecret(), $authDuration);
-
- if (!$sessionId) {
- return;
- }
-
- foreach ($sessions as $session) {
- if ($sessionId === $session->getId()) {
- return $session;
- }
- }
-
+App::setResource('session', function (Document $user) {
+ if ($user->isEmpty()) {
return;
- });
+ }
-$console
- ->setName('console')
- ->setCallback(function () {
- return new Document([
- '$id' => ID::custom('console'),
- '$internalId' => ID::custom('console'),
- 'name' => 'Appwrite',
- '$collection' => ID::custom('projects'),
- 'description' => 'Appwrite core engine',
- 'logo' => '',
- 'teamId' => -1,
- 'webhooks' => [],
- 'keys' => [],
- 'platforms' => [
- [
- '$collection' => ID::custom('platforms'),
- 'name' => 'Localhost',
- 'type' => Origin::CLIENT_TYPE_WEB,
- 'hostname' => 'localhost',
- ], // Current host is added on app init
- ],
- 'legalName' => '',
- 'legalCountry' => '',
- 'legalState' => '',
- 'legalCity' => '',
- 'legalAddress' => '',
- 'legalTaxId' => '',
- 'auths' => [
- 'mockNumbers' => [],
- 'invites' => System::getEnv('_APP_CONSOLE_INVITES', 'enabled') === 'enabled',
- 'limit' => (System::getEnv('_APP_CONSOLE_WHITELIST_ROOT', 'enabled') === 'enabled') ? 1 : 0, // limit signup to 1 user
- 'duration' => Auth::TOKEN_EXPIRATION_LOGIN_LONG, // 1 Year in seconds
- 'sessionAlerts' => System::getEnv('_APP_CONSOLE_SESSION_ALERTS', 'disabled') === 'enabled'
- ],
- 'authWhitelistEmails' => (!empty(System::getEnv('_APP_CONSOLE_WHITELIST_EMAILS', null))) ? \explode(',', System::getEnv('_APP_CONSOLE_WHITELIST_EMAILS', null)) : [],
- 'authWhitelistIPs' => (!empty(System::getEnv('_APP_CONSOLE_WHITELIST_IPS', null))) ? \explode(',', System::getEnv('_APP_CONSOLE_WHITELIST_IPS', null)) : [],
- 'oAuthProviders' => [
- 'githubEnabled' => true,
- 'githubSecret' => System::getEnv('_APP_CONSOLE_GITHUB_SECRET', ''),
- 'githubAppid' => System::getEnv('_APP_CONSOLE_GITHUB_APP_ID', '')
- ],
- ]);
- });
+ $sessions = $user->getAttribute('sessions', []);
+ $sessionId = Auth::sessionVerify($user->getAttribute('sessions'), Auth::$secret);
+ if (!$sessionId) {
+ return;
+ }
-$project
- ->setName('project')
- ->inject('dbForConsole')
- ->inject('request')
- ->inject('console')
- ->inject('authorization')
- ->setCallback(function (Database $dbForConsole, Request $request, Document $console, Authorization $authorization) {
- $projectId = $request->getParam('project', $request->getHeader('x-appwrite-project', ''));
-
- if (empty($projectId) || $projectId === 'console') {
- return $console;
+ foreach ($sessions as $session) {/** @var Document $session */
+ if ($sessionId === $session->getId()) {
+ return $session;
}
+ }
- $project = $authorization->skip(fn () => $dbForConsole->getDocument('projects', $projectId));
+ return;
+}, ['user']);
- return $project;
- });
+App::setResource('console', function () {
+ return new Document([
+ '$id' => ID::custom('console'),
+ '$internalId' => ID::custom('console'),
+ 'name' => 'Appwrite',
+ '$collection' => ID::custom('projects'),
+ 'description' => 'Appwrite core engine',
+ 'logo' => '',
+ 'teamId' => null,
+ 'webhooks' => [],
+ 'keys' => [],
+ 'platforms' => [
+ [
+ '$collection' => ID::custom('platforms'),
+ 'name' => 'Localhost',
+ 'type' => Origin::CLIENT_TYPE_WEB,
+ 'hostname' => 'localhost',
+ ], // Current host is added on app init
+ ],
+ 'legalName' => '',
+ 'legalCountry' => '',
+ 'legalState' => '',
+ 'legalCity' => '',
+ 'legalAddress' => '',
+ 'legalTaxId' => '',
+ 'auths' => [
+ 'mockNumbers' => [],
+ 'invites' => System::getEnv('_APP_CONSOLE_INVITES', 'enabled') === 'enabled',
+ 'limit' => (System::getEnv('_APP_CONSOLE_WHITELIST_ROOT', 'enabled') === 'enabled') ? 1 : 0, // limit signup to 1 user
+ 'duration' => Auth::TOKEN_EXPIRATION_LOGIN_LONG, // 1 Year in seconds
+ 'sessionAlerts' => System::getEnv('_APP_CONSOLE_SESSION_ALERTS', 'disabled') === 'enabled'
+ ],
+ 'authWhitelistEmails' => (!empty(System::getEnv('_APP_CONSOLE_WHITELIST_EMAILS', null))) ? \explode(',', System::getEnv('_APP_CONSOLE_WHITELIST_EMAILS', null)) : [],
+ 'authWhitelistIPs' => (!empty(System::getEnv('_APP_CONSOLE_WHITELIST_IPS', null))) ? \explode(',', System::getEnv('_APP_CONSOLE_WHITELIST_IPS', null)) : [],
+ 'oAuthProviders' => [
+ 'githubEnabled' => true,
+ 'githubSecret' => System::getEnv('_APP_CONSOLE_GITHUB_SECRET', ''),
+ 'githubAppid' => System::getEnv('_APP_CONSOLE_GITHUB_APP_ID', '')
+ ],
+ ]);
+}, []);
-$pools
- ->setName('pools')
- ->inject('registry')
- ->setCallback(function (Registry $registry) {
- return $registry->get('pools');
- });
+App::setResource('dbForProject', function (Group $pools, Database $dbForConsole, Cache $cache, Document $project) {
+ if ($project->isEmpty() || $project->getId() === 'console') {
+ return $dbForConsole;
+ }
-$dbForProject
- ->setName('dbForProject')
- ->inject('cache')
- ->inject('pools')
- ->inject('project')
- ->inject('dbForConsole')
- ->inject('authorization')
- ->inject('connections')
- ->setCallback(function (Cache $cache, array $pools, Document $project, Database $dbForConsole, Authorization $authorization, Connections $connections) {
+ try {
+ $dsn = new DSN($project->getAttribute('database'));
+ } catch (\InvalidArgumentException) {
+ // TODO: Temporary until all projects are using shared tables
+ $dsn = new DSN('mysql://' . $project->getAttribute('database'));
+ }
+
+ $dbAdapter = $pools
+ ->get($dsn->getHost())
+ ->pop()
+ ->getResource();
+
+ $database = new Database($dbAdapter, $cache);
+
+ $database
+ ->setMetadata('host', \gethostname())
+ ->setMetadata('project', $project->getId())
+ ->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS);
+
+ try {
+ $dsn = new DSN($project->getAttribute('database'));
+ } catch (\InvalidArgumentException) {
+ // TODO: Temporary until all projects are using shared tables
+ $dsn = new DSN('mysql://' . $project->getAttribute('database'));
+ }
+
+ if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) {
+ $database
+ ->setSharedTables(true)
+ ->setTenant($project->getInternalId())
+ ->setNamespace($dsn->getParam('namespace'));
+ } else {
+ $database
+ ->setSharedTables(false)
+ ->setTenant(null)
+ ->setNamespace('_' . $project->getInternalId());
+ }
+
+ return $database;
+}, ['pools', 'dbForConsole', 'cache', 'project']);
+
+App::setResource('dbForConsole', function (Group $pools, Cache $cache) {
+ $dbAdapter = $pools
+ ->get('console')
+ ->pop()
+ ->getResource();
+
+ $database = new Database($dbAdapter, $cache);
+
+ $database
+ ->setNamespace('_console')
+ ->setMetadata('host', \gethostname())
+ ->setMetadata('project', 'console')
+ ->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS);
+
+ return $database;
+}, ['pools', 'cache']);
+
+App::setResource('getProjectDB', function (Group $pools, Database $dbForConsole, $cache) {
+ $databases = []; // TODO: @Meldiron This should probably be responsibility of utopia-php/pools
+
+ return function (Document $project) use ($pools, $dbForConsole, $cache, &$databases) {
if ($project->isEmpty() || $project->getId() === 'console') {
return $dbForConsole;
}
@@ -781,449 +1454,13 @@ $dbForProject
$dsn = new DSN('mysql://' . $project->getAttribute('database'));
}
- $pool = $pools['pools-database-' . $dsn->getHost()]['pool'];
- $connectionDsn = $pools['pools-database-' . $dsn->getHost()]['dsn'];
-
- $connection = $pool->get();
- $connections->add($connection, $pool);
- $adapter = match ($connectionDsn->getScheme()) {
- 'mariadb' => new MariaDB($connection),
- 'mysql' => new MySQL($connection),
- default => null
- };
-
- $adapter->setDatabase($connectionDsn->getPath());
-
- $database = new Database($adapter, $cache);
-
- try {
- $dsn = new DSN($project->getAttribute('database'));
- } catch (\InvalidArgumentException) {
- // TODO: Temporary until all projects are using shared tables
- $dsn = new DSN('mysql://' . $project->getAttribute('database'));
- }
-
- if ($dsn->getHost() === DATABASE_SHARED_TABLES) {
+ $configure = (function (Database $database) use ($project, $dsn) {
$database
- ->setSharedTables(true)
- ->setTenant($project->getInternalId())
- ->setNamespace($dsn->getParam('namespace'));
- } else {
- $database
- ->setSharedTables(false)
- ->setTenant(null)
- ->setNamespace('_' . $project->getInternalId());
- }
-
- $database->setAuthorization($authorization);
- return $database;
- });
-
-$dbForConsole
- ->setName('dbForConsole')
- ->inject('getConsoleDB')
- ->inject('connections')
- ->setCallback(function (callable $getConsoleDB, Connections $connections): Database {
- [$connection,$pool, $database] = $getConsoleDB();
- $connections->add($connection, $pool);
-
- return $database;
- });
-
-$cache
- ->setName('cache')
- ->inject('pools')
- ->inject('connections')
- ->setCallback(function (array $pools, Connections $connections) {
- $adapters = [];
- $databases = Config::getParam('pools-cache');
-
- foreach ($databases as $database) {
- $pool = $pools['pools-cache-' . $database]['pool'];
- $dsn = $pools['pools-cache-' . $database]['dsn'];
-
- $connection = $pool->get();
- $connections->add($connection, $pool);
-
- $adapters[] = new CacheRedis($connection);
- }
-
- return new Cache(new Sharding($adapters));
- });
-
-$authorization
- ->setName('authorization')
- ->setCallback(function (): Authorization {
- return new Authorization();
- });
-
-$authentication
- ->setName('authentication')
- ->setCallback(function (): Authentication {
- return new Authentication();
- });
-
-$register
- ->setName('registry')
- ->setCallback(function () use (&$registry): Registry {
- return $registry;
- });
-
-$pools
- ->setName('pools')
- ->inject('registry')
- ->setCallback(function (Registry $registry) {
- return $registry->get('pools');
- });
-
-$logger
- ->setName('logger')
- ->inject('registry')
- ->setCallback(function (Registry $registry) {
- return $registry->get('logger');
- });
-
-$log
- ->setName('log')
- ->setCallback(function () {
- return new Log();
- });
-
-$connections
- ->setName('connections')
- ->setCallback(function () {
- return new Connections();
- });
-
-$locale
- ->setName('locale')
- ->setCallback(fn () => new Locale(System::getEnv('_APP_LOCALE', 'en')));
-
-$localeCodes
- ->setName('localeCodes')
- ->setCallback(fn () => array_map(fn ($locale) => $locale['code'], Config::getParam('locale-codes', [])));
-
-$queue
- ->setName('queue')
- ->inject('pools')
- ->inject('connections')
- ->setCallback(function (array $pools, Connections $connections) {
- $pool = $pools['pools-queue-queue']['pool'];
- $connection = $pool->get();
- $connections->add($connection, $pool);
-
- return new Queue\Connection\Redis($connection);
- });
-
-$queueForMessaging
- ->setName('queueForMessaging')
- ->inject('queue')
- ->setCallback(function (Connection $queue) {
- return new Messaging($queue);
- });
-
-$queueForMails
- ->setName('queueForMails')
- ->inject('queue')
- ->setCallback(function (Connection $queue) {
- return new Mail($queue);
- });
-
-$queueForBuilds
- ->setName('queueForBuilds')
- ->inject('queue')
- ->setCallback(function (Connection $queue) {
- return new Build($queue);
- });
-
-$queueForDatabase
- ->setName('queueForDatabase')
- ->inject('queue')
- ->setCallback(function (Connection $queue) {
- return new EventDatabase($queue);
- });
-
-$queueForDeletes
- ->setName('queueForDeletes')
- ->inject('queue')
- ->setCallback(function (Connection $queue) {
- return new Delete($queue);
- });
-
-$queueForEvents
- ->setName('queueForEvents')
- ->inject('queue')
- ->setCallback(function (Connection $queue) {
- return new Event($queue);
- });
-
-$queueForAudits
- ->setName('queueForAudits')
- ->inject('queue')
- ->setCallback(function (Connection $queue) {
- return new Audit($queue);
- });
-
-$queueForFunctions
- ->setName('queueForFunctions')
- ->inject('queue')
- ->setCallback(function (Connection $queue) {
- return new Func($queue);
- });
-
-$queueForUsage
- ->setName('queueForUsage')
- ->inject('queue')
- ->setCallback(function (Connection $queue) {
- return new Usage($queue);
- });
-
-$queueForCertificates
- ->setName('queueForCertificates')
- ->inject('queue')
- ->setCallback(function (Connection $queue) {
- return new Certificate($queue);
- });
-
-$queueForMigrations
- ->setName('queueForMigrations')
- ->inject('queue')
- ->setCallback(function (Connection $queue) {
- return new Migration($queue);
- });
-
-$deviceForLocal
- ->setName('deviceForLocal')
- ->setCallback(function () {
- return new Local();
- });
-
-$deviceForFiles
- ->setName('deviceForFiles')
- ->inject('project')
- ->setCallback(function ($project) {
- return getDevice(APP_STORAGE_UPLOADS . '/app-' . $project->getId());
- });
-
-$deviceForFunctions
- ->setName('deviceForFunctions')
- ->inject('project')
- ->setCallback(function ($project) {
- return getDevice(APP_STORAGE_FUNCTIONS . '/app-' . $project->getId());
- });
-
-$deviceForBuilds
- ->setName('deviceForBuilds')
- ->inject('project')
- ->setCallback(function ($project) {
- return getDevice(APP_STORAGE_BUILDS . '/app-' . $project->getId());
- });
-
-$clients
- ->setName('clients')
- ->inject('request')
- ->inject('console')
- ->inject('project')
- ->setCallback(function (Request $request, Document $console, Document $project) {
- $console->setAttribute('platforms', [ // Always allow current host
- '$collection' => ID::custom('platforms'),
- 'name' => 'Current Host',
- 'type' => Origin::CLIENT_TYPE_WEB,
- 'hostname' => $request->getHostname(),
- ], Document::SET_TYPE_APPEND);
-
- $hostnames = explode(',', System::getEnv('_APP_CONSOLE_HOSTNAMES', ''));
- $validator = new Hostname();
- foreach ($hostnames as $hostname) {
- $hostname = trim($hostname);
- if (!$validator->isValid($hostname)) {
- continue;
- }
- $console->setAttribute('platforms', [
- '$collection' => ID::custom('platforms'),
- 'type' => Origin::CLIENT_TYPE_WEB,
- 'name' => $hostname,
- 'hostname' => $hostname,
- ], Document::SET_TYPE_APPEND);
- }
-
- /**
- * Get All verified client URLs for both console and current projects
- * + Filter for duplicated entries
- */
- $clientsConsole = \array_map(
- fn ($node) => $node['hostname'],
- \array_filter(
- $console->getAttribute('platforms', []),
- fn ($node) => (isset($node['type']) && ($node['type'] === Origin::CLIENT_TYPE_WEB) && isset($node['hostname']) && !empty($node['hostname']))
- )
- );
-
- $clients = \array_unique(
- \array_merge(
- $clientsConsole,
- \array_map(
- fn ($node) => $node['hostname'],
- \array_filter(
- $project->getAttribute('platforms', []),
- fn ($node) => (isset($node['type']) && ($node['type'] === Origin::CLIENT_TYPE_WEB || $node['type'] === Origin::CLIENT_TYPE_FLUTTER_WEB) && isset($node['hostname']) && !empty($node['hostname']))
- )
- )
- )
- );
-
- return $clients;
- });
-
-$servers
- ->setName('servers')
- ->setCallback(function () {
- $platforms = Config::getParam('platforms');
- $server = $platforms[APP_PLATFORM_SERVER];
-
- $languages = array_map(function ($language) {
- return strtolower($language['name']);
- }, $server['sdks']);
-
- return $languages;
- });
-
-$geodb
- ->setName('geodb')
- ->inject('registry')
- ->setCallback(function (Registry $register) {
- return $register->get('geodb');
- });
-
-$passwordsDictionary
- ->setName('passwordsDictionary')
- ->setCallback(function () {
- $content = file_get_contents(__DIR__ . '/assets/security/10k-common-passwords');
- $content = explode("\n", $content);
- $content = array_flip($content);
- return $content;
- });
-
-$hooks
- ->setName('hooks')
- ->inject('registry')
- ->setCallback(function (Registry $registry) {
- return $registry->get('hooks');
- });
-
-$github
- ->setName('gitHub')
- ->inject('cache')
- ->setCallback(function (Cache $cache) {
- return new GitHub($cache);
- });
-
-$requestTimestamp
- ->setName('requestTimestamp')
- ->inject('request')
- ->setCallback(function ($request) {
- $timestampHeader = $request->getHeader('x-appwrite-timestamp');
- $requestTimestamp = null;
- if (!empty($timestampHeader)) {
- try {
- $requestTimestamp = new \DateTime($timestampHeader);
- } catch (\Throwable $e) {
- throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Invalid X-Appwrite-Timestamp header value');
- }
- }
- return $requestTimestamp;
- });
-$getConsoleDB
- ->setName('getConsoleDB')
- ->inject('pools')
- ->inject('cache')
- ->inject('authorization')
- ->inject('connections')
- ->setCallback(function (array $pools, Cache $cache, Authorization $authorization) {
- return function () use ($pools, $cache, $authorization): array {
- $pool = $pools['pools-console-console']['pool'];
- $dsn = $pools['pools-console-console']['dsn'];
- $connection = $pool->get();
-
- $adapter = match ($dsn->getScheme()) {
- 'mariadb' => new MariaDB($connection),
- 'mysql' => new MySQL($connection),
- default => null
- };
-
- $adapter->setDatabase($dsn->getPath());
-
- $database = new Database($adapter, $cache);
- $database->setAuthorization($authorization);
-
- $database
- ->setNamespace('_console')
->setMetadata('host', \gethostname())
- ->setMetadata('project', 'console')
+ ->setMetadata('project', $project->getId())
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS);
- return [$connection, $pool, $database];
- };
- });
-
-$getProjectDB
- ->setName('getProjectDB')
- ->inject('pools')
- ->inject('dbForConsole')
- ->inject('cache')
- ->inject('authorization')
- ->inject('connections')
- ->setCallback(function (array $pools, Database $dbForConsole, Cache $cache, Authorization $authorization, Connections $connections) {
- $databases = []; // TODO: @Meldiron This should probably be responsibility of utopia-php/pools
-
- return function (Document $project) use ($pools, $dbForConsole, $cache, &$databases, $authorization, $connections): Database {
- if ($project->isEmpty() || $project->getId() === 'console') {
- return $dbForConsole;
- }
-
- try {
- $dsn = new DSN($project->getAttribute('database'));
- } catch (\InvalidArgumentException) {
- // TODO: Temporary until all projects are using shared tables
- $dsn = new DSN('mysql://' . $project->getAttribute('database'));
- }
-
- if (isset($databases[$dsn->getHost()])) {
- $database = $databases[$dsn->getHost()];
-
- if ($dsn->getHost() === DATABASE_SHARED_TABLES) {
- $database
- ->setSharedTables(true)
- ->setTenant($project->getInternalId())
- ->setNamespace($dsn->getParam('namespace'));
- } else {
- $database
- ->setSharedTables(false)
- ->setTenant(null)
- ->setNamespace('_' . $project->getInternalId());
- }
-
- return $database;
- }
-
- $pool = $pools['pools-database-' . $dsn->getHost()]['pool'];
- $connectionDsn = $pools['pools-database-' . $dsn->getHost()]['dsn'];
-
- $connection = $pool->get();
- $connections->add($connection, $pool);
- $adapter = match ($connectionDsn->getScheme()) {
- 'mariadb' => new MariaDB($connection),
- 'mysql' => new MySQL($connection),
- default => null
- };
- $adapter->setDatabase($connectionDsn->getPath());
-
- $database = new Database($adapter, $cache);
- $database->setAuthorization($authorization);
-
- $databases[$dsn->getHost()] = $database;
-
- if ($dsn->getHost() === DATABASE_SHARED_TABLES) {
+ if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) {
$database
->setSharedTables(true)
->setTenant($project->getInternalId())
@@ -1234,161 +1471,334 @@ $getProjectDB
->setTenant(null)
->setNamespace('_' . $project->getInternalId());
}
+ });
+ if (isset($databases[$dsn->getHost()])) {
+ $database = $databases[$dsn->getHost()];
+ $configure($database);
return $database;
- };
+ }
+
+ $dbAdapter = $pools
+ ->get($dsn->getHost())
+ ->pop()
+ ->getResource();
+
+ $database = new Database($dbAdapter, $cache);
+ $databases[$dsn->getHost()] = $database;
+ $configure($database);
+
+ return $database;
+ };
+}, ['pools', 'dbForConsole', 'cache']);
+
+App::setResource('cache', function (Group $pools) {
+ $list = Config::getParam('pools-cache', []);
+ $adapters = [];
+
+ foreach ($list as $value) {
+ $adapters[] = $pools
+ ->get($value)
+ ->pop()
+ ->getResource()
+ ;
+ }
+
+ return new Cache(new Sharding($adapters));
+}, ['pools']);
+
+App::setResource('deviceForLocal', function () {
+ return new Local();
+});
+
+App::setResource('deviceForFiles', function ($project) {
+ return getDevice(APP_STORAGE_UPLOADS . '/app-' . $project->getId());
+}, ['project']);
+
+App::setResource('deviceForFunctions', function ($project) {
+ return getDevice(APP_STORAGE_FUNCTIONS . '/app-' . $project->getId());
+}, ['project']);
+
+App::setResource('deviceForBuilds', function ($project) {
+ return getDevice(APP_STORAGE_BUILDS . '/app-' . $project->getId());
+}, ['project']);
+
+function getDevice(string $root, string $connection = ''): Device
+{
+ $connection = !empty($connection) ? $connection : System::getEnv('_APP_CONNECTIONS_STORAGE', '');
+
+ if (!empty($connection)) {
+ $acl = 'private';
+ $device = Storage::DEVICE_LOCAL;
+ $accessKey = '';
+ $accessSecret = '';
+ $bucket = '';
+ $region = '';
+
+ try {
+ $dsn = new DSN($connection);
+ $device = $dsn->getScheme();
+ $accessKey = $dsn->getUser() ?? '';
+ $accessSecret = $dsn->getPassword() ?? '';
+ $bucket = $dsn->getPath() ?? '';
+ $region = $dsn->getParam('region');
+ } catch (\Throwable $e) {
+ Console::warning($e->getMessage() . 'Invalid DSN. Defaulting to Local device.');
+ }
+
+ switch ($device) {
+ case Storage::DEVICE_S3:
+ return new S3($root, $accessKey, $accessSecret, $bucket, $region, $acl);
+ case STORAGE::DEVICE_DO_SPACES:
+ $device = new DOSpaces($root, $accessKey, $accessSecret, $bucket, $region, $acl);
+ $device->setHttpVersion(S3::HTTP_VERSION_1_1);
+ return $device;
+ case Storage::DEVICE_BACKBLAZE:
+ return new Backblaze($root, $accessKey, $accessSecret, $bucket, $region, $acl);
+ case Storage::DEVICE_LINODE:
+ return new Linode($root, $accessKey, $accessSecret, $bucket, $region, $acl);
+ case Storage::DEVICE_WASABI:
+ return new Wasabi($root, $accessKey, $accessSecret, $bucket, $region, $acl);
+ case Storage::DEVICE_LOCAL:
+ default:
+ return new Local($root);
+ }
+ } else {
+ switch (strtolower(System::getEnv('_APP_STORAGE_DEVICE', Storage::DEVICE_LOCAL) ?? '')) {
+ case Storage::DEVICE_LOCAL:
+ default:
+ return new Local($root);
+ case Storage::DEVICE_S3:
+ $s3AccessKey = System::getEnv('_APP_STORAGE_S3_ACCESS_KEY', '');
+ $s3SecretKey = System::getEnv('_APP_STORAGE_S3_SECRET', '');
+ $s3Region = System::getEnv('_APP_STORAGE_S3_REGION', '');
+ $s3Bucket = System::getEnv('_APP_STORAGE_S3_BUCKET', '');
+ $s3Acl = 'private';
+ return new S3($root, $s3AccessKey, $s3SecretKey, $s3Bucket, $s3Region, $s3Acl);
+ case Storage::DEVICE_DO_SPACES:
+ $doSpacesAccessKey = System::getEnv('_APP_STORAGE_DO_SPACES_ACCESS_KEY', '');
+ $doSpacesSecretKey = System::getEnv('_APP_STORAGE_DO_SPACES_SECRET', '');
+ $doSpacesRegion = System::getEnv('_APP_STORAGE_DO_SPACES_REGION', '');
+ $doSpacesBucket = System::getEnv('_APP_STORAGE_DO_SPACES_BUCKET', '');
+ $doSpacesAcl = 'private';
+ $device = new DOSpaces($root, $doSpacesAccessKey, $doSpacesSecretKey, $doSpacesBucket, $doSpacesRegion, $doSpacesAcl);
+ $device->setHttpVersion(S3::HTTP_VERSION_1_1);
+ return $device;
+ case Storage::DEVICE_BACKBLAZE:
+ $backblazeAccessKey = System::getEnv('_APP_STORAGE_BACKBLAZE_ACCESS_KEY', '');
+ $backblazeSecretKey = System::getEnv('_APP_STORAGE_BACKBLAZE_SECRET', '');
+ $backblazeRegion = System::getEnv('_APP_STORAGE_BACKBLAZE_REGION', '');
+ $backblazeBucket = System::getEnv('_APP_STORAGE_BACKBLAZE_BUCKET', '');
+ $backblazeAcl = 'private';
+ return new Backblaze($root, $backblazeAccessKey, $backblazeSecretKey, $backblazeBucket, $backblazeRegion, $backblazeAcl);
+ case Storage::DEVICE_LINODE:
+ $linodeAccessKey = System::getEnv('_APP_STORAGE_LINODE_ACCESS_KEY', '');
+ $linodeSecretKey = System::getEnv('_APP_STORAGE_LINODE_SECRET', '');
+ $linodeRegion = System::getEnv('_APP_STORAGE_LINODE_REGION', '');
+ $linodeBucket = System::getEnv('_APP_STORAGE_LINODE_BUCKET', '');
+ $linodeAcl = 'private';
+ return new Linode($root, $linodeAccessKey, $linodeSecretKey, $linodeBucket, $linodeRegion, $linodeAcl);
+ case Storage::DEVICE_WASABI:
+ $wasabiAccessKey = System::getEnv('_APP_STORAGE_WASABI_ACCESS_KEY', '');
+ $wasabiSecretKey = System::getEnv('_APP_STORAGE_WASABI_SECRET', '');
+ $wasabiRegion = System::getEnv('_APP_STORAGE_WASABI_REGION', '');
+ $wasabiBucket = System::getEnv('_APP_STORAGE_WASABI_BUCKET', '');
+ $wasabiAcl = 'private';
+ return new Wasabi($root, $wasabiAccessKey, $wasabiSecretKey, $wasabiBucket, $wasabiRegion, $wasabiAcl);
+ }
+ }
+}
+
+App::setResource('mode', function ($request) {
+ /** @var Appwrite\Utopia\Request $request */
+
+ /**
+ * Defines the mode for the request:
+ * - 'default' => Requests for Client and Server Side
+ * - 'admin' => Request from the Console on non-console projects
+ */
+ return $request->getParam('mode', $request->getHeader('x-appwrite-mode', APP_MODE_DEFAULT));
+}, ['request']);
+
+App::setResource('geodb', function ($register) {
+ /** @var Utopia\Registry\Registry $register */
+ return $register->get('geodb');
+}, ['register']);
+
+App::setResource('passwordsDictionary', function ($register) {
+ /** @var Utopia\Registry\Registry $register */
+ return $register->get('passwordsDictionary');
+}, ['register']);
+
+
+App::setResource('servers', function () {
+ $platforms = Config::getParam('platforms');
+ $server = $platforms[APP_PLATFORM_SERVER];
+
+ $languages = array_map(function ($language) {
+ return strtolower($language['name']);
+ }, $server['sdks']);
+
+ return $languages;
+});
+
+App::setResource('promiseAdapter', function ($register) {
+ return $register->get('promiseAdapter');
+}, ['register']);
+
+App::setResource('schema', function ($utopia, $dbForProject) {
+
+ $complexity = function (int $complexity, array $args) {
+ $queries = Query::parseQueries($args['queries'] ?? []);
+ $query = Query::getByType($queries, [Query::TYPE_LIMIT])[0] ?? null;
+ $limit = $query ? $query->getValue() : APP_LIMIT_LIST_DEFAULT;
+
+ return $complexity * $limit;
+ };
+
+ $attributes = function (int $limit, int $offset) use ($dbForProject) {
+ $attrs = Authorization::skip(fn () => $dbForProject->find('attributes', [
+ Query::limit($limit),
+ Query::offset($offset),
+ ]));
+
+ return \array_map(function ($attr) {
+ return $attr->getArrayCopy();
+ }, $attrs);
+ };
+
+ $urls = [
+ 'list' => function (string $databaseId, string $collectionId, array $args) {
+ return "/v1/databases/$databaseId/collections/$collectionId/documents";
+ },
+ 'create' => function (string $databaseId, string $collectionId, array $args) {
+ return "/v1/databases/$databaseId/collections/$collectionId/documents";
+ },
+ 'read' => function (string $databaseId, string $collectionId, array $args) {
+ return "/v1/databases/$databaseId/collections/$collectionId/documents/{$args['documentId']}";
+ },
+ 'update' => function (string $databaseId, string $collectionId, array $args) {
+ return "/v1/databases/$databaseId/collections/$collectionId/documents/{$args['documentId']}";
+ },
+ 'delete' => function (string $databaseId, string $collectionId, array $args) {
+ return "/v1/databases/$databaseId/collections/$collectionId/documents/{$args['documentId']}";
+ },
+ ];
+
+ $params = [
+ 'list' => function (string $databaseId, string $collectionId, array $args) {
+ return [ 'queries' => $args['queries']];
+ },
+ 'create' => function (string $databaseId, string $collectionId, array $args) {
+ $id = $args['id'] ?? 'unique()';
+ $permissions = $args['permissions'] ?? null;
+
+ unset($args['id']);
+ unset($args['permissions']);
+
+ // Order must be the same as the route params
+ return [
+ 'databaseId' => $databaseId,
+ 'documentId' => $id,
+ 'collectionId' => $collectionId,
+ 'data' => $args,
+ 'permissions' => $permissions,
+ ];
+ },
+ 'update' => function (string $databaseId, string $collectionId, array $args) {
+ $documentId = $args['id'];
+ $permissions = $args['permissions'] ?? null;
+
+ unset($args['id']);
+ unset($args['permissions']);
+
+ // Order must be the same as the route params
+ return [
+ 'databaseId' => $databaseId,
+ 'collectionId' => $collectionId,
+ 'documentId' => $documentId,
+ 'data' => $args,
+ 'permissions' => $permissions,
+ ];
+ },
+ ];
+
+ return Schema::build(
+ $utopia,
+ $complexity,
+ $attributes,
+ $urls,
+ $params,
+ );
+}, ['utopia', 'dbForProject']);
+
+App::setResource('contributors', function () {
+ $path = 'app/config/contributors.json';
+ $list = (file_exists($path)) ? json_decode(file_get_contents($path), true) : [];
+ return $list;
+});
+
+App::setResource('employees', function () {
+ $path = 'app/config/employees.json';
+ $list = (file_exists($path)) ? json_decode(file_get_contents($path), true) : [];
+ return $list;
+});
+
+App::setResource('heroes', function () {
+ $path = 'app/config/heroes.json';
+ $list = (file_exists($path)) ? json_decode(file_get_contents($path), true) : [];
+ return $list;
+});
+
+App::setResource('gitHub', function (Cache $cache) {
+ return new VcsGitHub($cache);
+}, ['cache']);
+
+App::setResource('requestTimestamp', function ($request) {
+ //TODO: Move this to the Request class itself
+ $timestampHeader = $request->getHeader('x-appwrite-timestamp');
+ $requestTimestamp = null;
+ if (!empty($timestampHeader)) {
+ try {
+ $requestTimestamp = new \DateTime($timestampHeader);
+ } catch (\Throwable $e) {
+ throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Invalid X-Appwrite-Timestamp header value');
+ }
+ }
+ return $requestTimestamp;
+}, ['request']);
+App::setResource('plan', function (array $plan = []) {
+ return [];
+});
+
+
+App::setResource('team', function (Document $project, Database $dbForConsole, App $utopia, Request $request) {
+ $teamInternalId = '';
+ if ($project->getId() !== 'console') {
+ $teamInternalId = $project->getAttribute('teamInternalId', '');
+ } else {
+ $route = $utopia->match($request);
+ $path = $route->getPath();
+ if (str_starts_with($path, '/v1/projects/:projectId')) {
+ $uri = $request->getURI();
+ $pid = explode('/', $uri)[3];
+ $p = Authorization::skip(fn () => $dbForConsole->getDocument('projects', $pid));
+ $teamInternalId = $p->getAttribute('teamInternalId', '');
+ } elseif ($path === '/v1/projects') {
+ $teamId = $request->getParam('teamId', '');
+ $team = Authorization::skip(fn () => $dbForConsole->getDocument('teams', $teamId));
+ return $team;
+ }
+ }
+
+ $team = Authorization::skip(function () use ($dbForConsole, $teamInternalId) {
+ return $dbForConsole->findOne('teams', [
+ Query::equal('$internalId', [$teamInternalId]),
+ ]);
});
-$promiseAdapter
- ->setName('promiseAdapter')
- ->setCallback(function () use ($registry) {
- return $registry->get('promiseAdapter');
- });
-
-$schemaVariable
- ->setName('schemaVariable')
- ->setCallback(fn () => new Schema());
-
-$schema
- ->setName('schema')
- ->inject('http')
- ->inject('context')
- ->inject('request')
- ->inject('response')
- ->inject('dbForProject')
- ->inject('authorization')
- ->inject('schemaVariable')
- ->setCallback(function (Http $http, Container $context, Request $request, Response $response, Database $dbForProject, Authorization $authorization, $schemaVariable) {
- $complexity = function (int $complexity, array $args) {
- $queries = Query::parseQueries($args['queries'] ?? []);
- $query = Query::getByType($queries, [Query::TYPE_LIMIT])[0] ?? null;
- $limit = $query ? $query->getValue() : APP_LIMIT_LIST_DEFAULT;
-
- return $complexity * $limit;
- };
-
- $attributes = function (int $limit, int $offset) use ($dbForProject, $authorization) {
- $attrs = $authorization->skip(fn () => $dbForProject->find('attributes', [
- Query::limit($limit),
- Query::offset($offset),
- ]));
-
- return \array_map(function ($attr) {
- return $attr->getArrayCopy();
- }, $attrs);
- };
-
- $urls = [
- 'list' => function (string $databaseId, string $collectionId, array $args) {
- return "/v1/databases/$databaseId/collections/$collectionId/documents";
- },
- 'create' => function (string $databaseId, string $collectionId, array $args) {
- return "/v1/databases/$databaseId/collections/$collectionId/documents";
- },
- 'read' => function (string $databaseId, string $collectionId, array $args) {
- return "/v1/databases/$databaseId/collections/$collectionId/documents/{$args['documentId']}";
- },
- 'update' => function (string $databaseId, string $collectionId, array $args) {
- return "/v1/databases/$databaseId/collections/$collectionId/documents/{$args['documentId']}";
- },
- 'delete' => function (string $databaseId, string $collectionId, array $args) {
- return "/v1/databases/$databaseId/collections/$collectionId/documents/{$args['documentId']}";
- },
- ];
-
- $params = [
- 'list' => function (string $databaseId, string $collectionId, array $args) {
- return ['queries' => $args['queries']];
- },
- 'create' => function (string $databaseId, string $collectionId, array $args) {
- $id = $args['id'] ?? 'unique()';
- $permissions = $args['permissions'] ?? null;
-
- unset($args['id']);
- unset($args['permissions']);
-
- return [
- 'databaseId' => $databaseId,
- 'documentId' => $id,
- 'collectionId' => $collectionId,
- 'data' => $args,
- 'permissions' => $permissions,
- ];
- },
- 'update' => function (string $databaseId, string $collectionId, array $args) {
- $documentId = $args['id'];
- $permissions = $args['permissions'] ?? null;
-
- unset($args['id']);
- unset($args['permissions']);
-
- return [
- 'databaseId' => $databaseId,
- 'collectionId' => $collectionId,
- 'documentId' => $documentId,
- 'data' => $args,
- 'permissions' => $permissions,
- ];
- },
- ];
-
- return $schemaVariable->build(
- $http,
- $request,
- $response,
- $context,
- $complexity,
- $attributes,
- $urls,
- $params,
- );
- });
-
-$container->set($log);
-$container->set($mode);
-$container->set($user);
-$container->set($plan);
-$container->set($cache);
-$container->set($pools);
-$container->set($queue);
-$container->set($geodb);
-$container->set($hooks);
-$container->set($locale);
-$container->set($schema);
-$container->set($github);
-$container->set($logger);
-$container->set($session);
-$container->set($console);
-$container->set($project);
-$container->set($clients);
-$container->set($servers);
-$container->set($register);
-$container->set($connections);
-$container->set($localeCodes);
-$container->set($dbForProject);
-$container->set($dbForConsole);
-$container->set($getConsoleDB);
-$container->set($getProjectDB);
-$container->set($queueForUsage);
-$container->set($queueForMails);
-$container->set($authorization);
-$container->set($authentication);
-$container->set($schemaVariable);
-$container->set($queueForBuilds);
-$container->set($queueForEvents);
-$container->set($queueForAudits);
-$container->set($deviceForLocal);
-$container->set($deviceForFiles);
-$container->set($promiseAdapter);
-$container->set($queueForDeletes);
-$container->set($deviceForBuilds);
-$container->set($queueForDatabase);
-$container->set($requestTimestamp);
-$container->set($queueForMessaging);
-$container->set($queueForFunctions);
-$container->set($queueForMigrations);
-$container->set($deviceForFunctions);
-$container->set($passwordsDictionary);
-$container->set($queueForCertificates);
-
-Models::init();
+ if (!$team) {
+ $team = new Document([]);
+ }
+ return $team;
+}, ['project', 'dbForConsole', 'utopia', 'request']);
diff --git a/app/init/config.php b/app/init/config.php
deleted file mode 100644
index bc8333b5d9..0000000000
--- a/app/init/config.php
+++ /dev/null
@@ -1,37 +0,0 @@
- $value], JSON_PRESERVE_ZERO_FRACTION);
- },
- function (mixed $value) {
- if (is_null($value)) {
- return;
- }
-
- return json_decode($value, true)['value'];
- }
-);
-
-Database::addFilter(
- 'enum',
- function (mixed $value, Document $attribute) {
- if ($attribute->isSet('elements')) {
- $attribute->removeAttribute('elements');
- }
-
- return $value;
- },
- function (mixed $value, Document $attribute) {
- $formatOptions = \json_decode($attribute->getAttribute('formatOptions', '[]'), true);
- if (isset($formatOptions['elements'])) {
- $attribute->setAttribute('elements', $formatOptions['elements']);
- }
-
- return $value;
- }
-);
-
-Database::addFilter(
- 'range',
- function (mixed $value, Document $attribute) {
- if ($attribute->isSet('min')) {
- $attribute->removeAttribute('min');
- }
- if ($attribute->isSet('max')) {
- $attribute->removeAttribute('max');
- }
-
- return $value;
- },
- function (mixed $value, Document $attribute) {
- $formatOptions = json_decode($attribute->getAttribute('formatOptions', '[]'), true);
- if (isset($formatOptions['min']) || isset($formatOptions['max'])) {
- $attribute
- ->setAttribute('min', $formatOptions['min'])
- ->setAttribute('max', $formatOptions['max'])
- ;
- }
-
- return $value;
- }
-);
-
-Database::addFilter(
- 'subQueryAttributes',
- function (mixed $value) {
- return;
- },
- function (mixed $value, Document $document, Database $database) {
- $attributes = $database->find('attributes', [
- Query::equal('collectionInternalId', [$document->getInternalId()]),
- Query::equal('databaseInternalId', [$document->getAttribute('databaseInternalId')]),
- Query::limit($database->getLimitForAttributes()),
- ]);
-
- foreach ($attributes as $attribute) {
- if ($attribute->getAttribute('type') === Database::VAR_RELATIONSHIP) {
- $options = $attribute->getAttribute('options');
- foreach ($options as $key => $value) {
- $attribute->setAttribute($key, $value);
- }
- $attribute->removeAttribute('options');
- }
- }
-
- return $attributes;
- }
-);
-
-Database::addFilter(
- 'subQueryIndexes',
- function (mixed $value) {
- return;
- },
- function (mixed $value, Document $document, Database $database) {
- return $database
- ->find('indexes', [
- Query::equal('collectionInternalId', [$document->getInternalId()]),
- Query::equal('databaseInternalId', [$document->getAttribute('databaseInternalId')]),
- Query::limit($database->getLimitForIndexes()),
- ]);
- }
-);
-
-Database::addFilter(
- 'subQueryPlatforms',
- function (mixed $value) {
- return;
- },
- function (mixed $value, Document $document, Database $database) {
- return $database
- ->find('platforms', [
- Query::equal('projectInternalId', [$document->getInternalId()]),
- Query::limit(APP_LIMIT_SUBQUERY),
- ]);
- }
-);
-
-Database::addFilter(
- 'subQueryKeys',
- function (mixed $value) {
- return;
- },
- function (mixed $value, Document $document, Database $database) {
- return $database
- ->find('keys', [
- Query::equal('projectInternalId', [$document->getInternalId()]),
- Query::limit(APP_LIMIT_SUBQUERY),
- ]);
- }
-);
-
-Database::addFilter(
- 'subQueryWebhooks',
- function (mixed $value) {
- return;
- },
- function (mixed $value, Document $document, Database $database) {
- return $database
- ->find('webhooks', [
- Query::equal('projectInternalId', [$document->getInternalId()]),
- Query::limit(APP_LIMIT_SUBQUERY),
- ]);
- }
-);
-
-Database::addFilter(
- 'subQuerySessions',
- function (mixed $value) {
- return;
- },
- function (mixed $value, Document $document, Database $database) {
- return $database->getAuthorization()->skip(fn () => $database->find('sessions', [
- Query::equal('userInternalId', [$document->getInternalId()]),
- Query::limit(APP_LIMIT_SUBQUERY),
- ]));
- }
-);
-
-Database::addFilter(
- 'subQueryTokens',
- function (mixed $value) {
- return;
- },
- function (mixed $value, Document $document, Database $database) {
- return $database->getAuthorization()->skip(fn () => $database
- ->find('tokens', [
- Query::equal('userInternalId', [$document->getInternalId()]),
- Query::limit(APP_LIMIT_SUBQUERY),
- ]));
- }
-);
-
-Database::addFilter(
- 'subQueryChallenges',
- function (mixed $value) {
- return;
- },
- function (mixed $value, Document $document, Database $database) {
- return $database->getAuthorization()->skip(fn () => $database
- ->find('challenges', [
- Query::equal('userInternalId', [$document->getInternalId()]),
- Query::limit(APP_LIMIT_SUBQUERY),
- ]));
- }
-);
-
-Database::addFilter(
- 'subQueryAuthenticators',
- function (mixed $value) {
- return;
- },
- function (mixed $value, Document $document, Database $database) {
- return $database->getAuthorization()->skip(fn () => $database
- ->find('authenticators', [
- Query::equal('userInternalId', [$document->getInternalId()]),
- Query::limit(APP_LIMIT_SUBQUERY),
- ]));
- }
-);
-
-Database::addFilter(
- 'subQueryMemberships',
- function (mixed $value) {
- return;
- },
- function (mixed $value, Document $document, Database $database) {
- return $database->getAuthorization()->skip(fn () => $database
- ->find('memberships', [
- Query::equal('userInternalId', [$document->getInternalId()]),
- Query::limit(APP_LIMIT_SUBQUERY),
- ]));
- }
-);
-
-Database::addFilter(
- 'subQueryVariables',
- function (mixed $value) {
- return;
- },
- function (mixed $value, Document $document, Database $database) {
- return $database
- ->find('variables', [
- Query::equal('resourceInternalId', [$document->getInternalId()]),
- Query::equal('resourceType', ['function']),
- Query::limit(APP_LIMIT_SUBQUERY),
- ]);
- }
-);
-
-Database::addFilter(
- 'encrypt',
- function (mixed $value) {
- $key = System::getEnv('_APP_OPENSSL_KEY_V1');
- $iv = OpenSSL::randomPseudoBytes(OpenSSL::cipherIVLength(OpenSSL::CIPHER_AES_128_GCM));
- $tag = null;
-
- return json_encode([
- 'data' => OpenSSL::encrypt($value, OpenSSL::CIPHER_AES_128_GCM, $key, 0, $iv, $tag),
- 'method' => OpenSSL::CIPHER_AES_128_GCM,
- 'iv' => \bin2hex($iv),
- 'tag' => \bin2hex($tag ?? ''),
- 'version' => '1',
- ]);
- },
- function (mixed $value) {
- if (is_null($value)) {
- return;
- }
- $value = json_decode($value, true);
- $key = System::getEnv('_APP_OPENSSL_KEY_V' . $value['version']);
-
- return OpenSSL::decrypt($value['data'], $value['method'], $key, 0, hex2bin($value['iv']), hex2bin($value['tag']));
- }
-);
-
-Database::addFilter(
- 'subQueryProjectVariables',
- function (mixed $value) {
- return;
- },
- function (mixed $value, Document $document, Database $database) {
- return $database
- ->find('variables', [
- Query::equal('resourceType', ['project']),
- Query::limit(APP_LIMIT_SUBQUERY)
- ]);
- }
-);
-
-Database::addFilter(
- 'userSearch',
- function (mixed $value, Document $user) {
- $searchValues = [
- $user->getId(),
- $user->getAttribute('email', ''),
- $user->getAttribute('name', ''),
- $user->getAttribute('phone', '')
- ];
-
- foreach ($user->getAttribute('labels', []) as $label) {
- $searchValues[] = 'label:' . $label;
- }
-
- $search = implode(' ', \array_filter($searchValues));
-
- return $search;
- },
- function (mixed $value) {
- return $value;
- }
-);
-
-Database::addFilter(
- 'subQueryTargets',
- function (mixed $value) {
- return;
- },
- function (mixed $value, Document $document, Database $database) {
- return $database->getAuthorization()->skip(fn () => $database
- ->find('targets', [
- Query::equal('userInternalId', [$document->getInternalId()]),
- Query::limit(APP_LIMIT_SUBQUERY)
- ]));
- }
-);
-
-Database::addFilter(
- 'subQueryTopicTargets',
- function (mixed $value) {
- return;
- },
- function (mixed $value, Document $document, Database $database) {
- $targetIds = $database->getAuthorization()->skip(fn () => \array_map(
- fn ($document) => $document->getAttribute('targetInternalId'),
- $database->find('subscribers', [
- Query::equal('topicInternalId', [$document->getInternalId()]),
- Query::limit(APP_LIMIT_SUBSCRIBERS_SUBQUERY)
- ])
- ));
- if (\count($targetIds) > 0) {
- return $database->find('targets', [
- Query::equal('$internalId', $targetIds)
- ]);
- }
- return [];
- }
-);
-
-Database::addFilter(
- 'providerSearch',
- function (mixed $value, Document $provider) {
- $searchValues = [
- $provider->getId(),
- $provider->getAttribute('name', ''),
- $provider->getAttribute('provider', ''),
- $provider->getAttribute('type', '')
- ];
-
- $search = \implode(' ', \array_filter($searchValues));
-
- return $search;
- },
- function (mixed $value) {
- return $value;
- }
-);
-
-Database::addFilter(
- 'topicSearch',
- function (mixed $value, Document $topic) {
- $searchValues = [
- $topic->getId(),
- $topic->getAttribute('name', ''),
- $topic->getAttribute('description', ''),
- ];
-
- $search = \implode(' ', \array_filter($searchValues));
-
- return $search;
- },
- function (mixed $value) {
- return $value;
- }
-);
-
-Database::addFilter(
- 'messageSearch',
- function (mixed $value, Document $message) {
- $searchValues = [
- $message->getId(),
- $message->getAttribute('description', ''),
- $message->getAttribute('status', ''),
- ];
-
- $data = \json_decode($message->getAttribute('data', []), true);
- $providerType = $message->getAttribute('providerType', '');
-
- if ($providerType === MESSAGE_TYPE_EMAIL) {
- $searchValues = \array_merge($searchValues, [$data['subject'], MESSAGE_TYPE_EMAIL]);
- } elseif ($providerType === MESSAGE_TYPE_SMS) {
- $searchValues = \array_merge($searchValues, [$data['content'], MESSAGE_TYPE_SMS]);
- } else {
- $searchValues = \array_merge($searchValues, [$data['title'], MESSAGE_TYPE_PUSH]);
- }
-
- $search = \implode(' ', \array_filter($searchValues));
-
- return $search;
- },
- function (mixed $value) {
- return $value;
- }
-);
diff --git a/app/init/database/formats.php b/app/init/database/formats.php
deleted file mode 100644
index 88e46655ac..0000000000
--- a/app/init/database/formats.php
+++ /dev/null
@@ -1,43 +0,0 @@
-get('pools');
+
+ $dbAdapter = $pools
+ ->get('console')
+ ->pop()
+ ->getResource()
+ ;
+
+ $database = new Database($dbAdapter, getCache());
+
+ $database
+ ->setNamespace('_console')
+ ->setMetadata('host', \gethostname())
+ ->setMetadata('project', '_console');
+
+ return $database;
+ }
+}
+
+// Allows overriding
+if (!function_exists('getProjectDB')) {
+ function getProjectDB(Document $project): Database
+ {
+ global $register;
+
+ /** @var \Utopia\Pools\Group $pools */
+ $pools = $register->get('pools');
+
+ if ($project->isEmpty() || $project->getId() === 'console') {
+ return getConsoleDB();
+ }
+
+ try {
+ $dsn = new DSN($project->getAttribute('database'));
+ } catch (\InvalidArgumentException) {
+ // TODO: Temporary until all projects are using shared tables
+ $dsn = new DSN('mysql://' . $project->getAttribute('database'));
+ }
+
+ $adapter = $pools
+ ->get($dsn->getHost())
+ ->pop()
+ ->getResource();
+
+ $database = new Database($adapter, getCache());
+
+ if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) {
+ $database
+ ->setSharedTables(true)
+ ->setTenant($project->getInternalId())
+ ->setNamespace($dsn->getParam('namespace'));
+ } else {
+ $database
+ ->setSharedTables(false)
+ ->setTenant(null)
+ ->setNamespace('_' . $project->getInternalId());
+ }
+
+ $database
+ ->setMetadata('host', \gethostname())
+ ->setMetadata('project', $project->getId());
+
+ return $database;
+ }
+}
+
+// Allows overriding
+if (!function_exists('getCache')) {
+ function getCache(): Cache
+ {
+ global $register;
+
+ $pools = $register->get('pools'); /** @var \Utopia\Pools\Group $pools */
+
+ $list = Config::getParam('pools-cache', []);
+ $adapters = [];
+
+ foreach ($list as $value) {
+ $adapters[] = $pools
+ ->get($value)
+ ->pop()
+ ->getResource()
+ ;
+ }
+
+ return new Cache(new Sharding($adapters));
+ }
+}
+
+if (!function_exists('getRealtime')) {
+ function getRealtime(): Realtime
+ {
+ return new Realtime();
+ }
+}
+
+$realtime = getRealtime();
/**
* Table for statistics across all workers.
@@ -70,8 +166,8 @@ $adapter
$server = new Server($adapter);
-$logError = function (Throwable $error, string $action) use ($registry) {
- $logger = $registry->get('logger');
+$logError = function (Throwable $error, string $action) use ($register) {
+ $logger = $register->get('logger');
if ($logger && !$error instanceof Exception) {
$version = System::getEnv('_APP_VERSION', 'UNKNOWN');
@@ -95,8 +191,12 @@ $logError = function (Throwable $error, string $action) use ($registry) {
$isProduction = System::getEnv('_APP_ENV', 'development') === 'production';
$log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING);
- $responseCode = $logger->addLog($log);
- Console::info('Realtime log pushed with status code: ' . $responseCode);
+ try {
+ $responseCode = $logger->addLog($log);
+ Console::info('Error log pushed with status code: ' . $responseCode);
+ } catch (Throwable $th) {
+ Console::error('Error pushing log: ' . $th->getMessage());
+ }
}
Console::error('[Error] Type: ' . get_class($error));
@@ -107,16 +207,16 @@ $logError = function (Throwable $error, string $action) use ($registry) {
$server->error($logError);
-$server->onStart(function () use ($stats, $container, $containerId, &$statsDocument, $logError) {
+$server->onStart(function () use ($stats, $register, $containerId, &$statsDocument, $logError) {
sleep(5); // wait for the initial database schema to be ready
Console::success('Server started successfully');
- $authorization = $container->get('authorization');
+
/**
* Create document for this worker to share stats across Containers.
*/
- go(function () use ($container, $containerId, &$statsDocument) {
+ go(function () use ($register, $containerId, &$statsDocument) {
$attempts = 0;
- $database = $container->get('dbForConsole');
+ $database = getConsoleDB();
do {
try {
@@ -130,15 +230,14 @@ $server->onStart(function () use ($stats, $container, $containerId, &$statsDocum
'value' => '{}'
]);
- $authorization = $container->get('authorization');
- $statsDocument = $authorization->skip(fn () => $database->createDocument('realtime', $document));
+ $statsDocument = Authorization::skip(fn () => $database->createDocument('realtime', $document));
break;
} catch (Throwable) {
Console::warning("Collection not ready. Retrying connection ({$attempts})...");
sleep(DATABASE_RECONNECT_SLEEP);
}
} while (true);
- ($container->get('connections'))->reclaim();
+ $register->get('pools')->reclaim();
});
/**
@@ -146,7 +245,7 @@ $server->onStart(function () use ($stats, $container, $containerId, &$statsDocum
*/
// TODO: Remove this if check once it doesn't cause issues for cloud
if (System::getEnv('_APP_EDITION', 'self-hosted') === 'self-hosted') {
- Timer::tick(5000, function () use ($container, $stats, &$statsDocument, $logError, $authorization) {
+ Timer::tick(5000, function () use ($register, $stats, &$statsDocument, $logError) {
$payload = [];
foreach ($stats as $projectId => $value) {
$payload[$projectId] = $stats->get($projectId, 'connectionsTotal');
@@ -156,43 +255,40 @@ $server->onStart(function () use ($stats, $container, $containerId, &$statsDocum
}
try {
- $database = $container->get('dbForConsole');
+ $database = getConsoleDB();
$statsDocument
->setAttribute('timestamp', DateTime::now())
->setAttribute('value', json_encode($payload));
- $authorization->skip(fn () => $database->updateDocument('realtime', $statsDocument->getId(), $statsDocument));
+ Authorization::skip(fn () => $database->updateDocument('realtime', $statsDocument->getId(), $statsDocument));
} catch (Throwable $th) {
call_user_func($logError, $th, "updateWorkerDocument");
} finally {
- ($container->get('connections'))->reclaim();
- $container->refresh('dbForConsole');
+ $register->get('pools')->reclaim();
}
});
}
});
-$server->onWorkerStart(function (int $workerId) use ($server, $container, $stats, $realtime, $logError) {
+$server->onWorkerStart(function (int $workerId) use ($server, $register, $stats, $realtime, $logError) {
Console::success('Worker ' . $workerId . ' started successfully');
$attempts = 0;
$start = time();
- $authorization = $container->get('authorization');
-
- Timer::tick(5000, function () use ($server, $container, $realtime, $stats, $logError, $authorization) {
+ Timer::tick(5000, function () use ($server, $register, $realtime, $stats, $logError) {
/**
* Sending current connections to project channels on the console project every 5 seconds.
*/
// TODO: Remove this if check once it doesn't cause issues for cloud
if (System::getEnv('_APP_EDITION', 'self-hosted') === 'self-hosted') {
if ($realtime->hasSubscriber('console', Role::users()->toString(), 'project')) {
- $database = $container->get('dbForConsole');
+ $database = getConsoleDB();
$payload = [];
- $list = $authorization->skip(fn () => $database->find('realtime', [
+ $list = Authorization::skip(fn () => $database->find('realtime', [
Query::greaterThan('timestamp', DateTime::addSeconds(new \DateTime(), -15)),
]));
@@ -232,8 +328,8 @@ $server->onWorkerStart(function (int $workerId) use ($server, $container, $stats
'data' => $event['data']
]));
}
- ($container->get('connections'))->reclaim();
- $container->refresh('dbForConsole');
+
+ $register->get('pools')->reclaim();
}
}
/**
@@ -263,26 +359,13 @@ $server->onWorkerStart(function (int $workerId) use ($server, $container, $stats
while ($attempts < 300) {
try {
if ($attempts > 0) {
- Console::error(
- 'Pub/sub connection lost (lasted ' . (time() - $start) . ' seconds, worker: ' . $workerId . ').
- Attempting restart in 5 seconds (attempt #' . $attempts . ')'
- );
+ Console::error('Pub/sub connection lost (lasted ' . (time() - $start) . ' seconds, worker: ' . $workerId . ').
+ Attempting restart in 5 seconds (attempt #' . $attempts . ')');
sleep(5); // 5 sec delay between connection attempts
}
-
$start = time();
- $pools = $container->get('pools');
- /** @var Connections $connections */
- $connections = $container->get('connections');
-
- $pool = $pools['pools-pubsub-pubsub']['pool'];
- $connection = $pool->get();
- $connections->add($connection, $pool);
-
- $redis = $connection;
-
- /** @var Redis $redis */
+ $redis = $register->get('pools')->get('pubsub')->pop()->getResource(); /** @var Redis $redis */
$redis->setOption(Redis::OPT_READ_TIMEOUT, -1);
if ($redis->ping(true)) {
@@ -292,7 +375,7 @@ $server->onWorkerStart(function (int $workerId) use ($server, $container, $stats
Console::error('Pub/sub failed (worker: ' . $workerId . ')');
}
- $redis->subscribe(['realtime'], function (Redis $redis, string $channel, string $payload) use ($server, $workerId, $stats, $realtime, $authorization, $container) {
+ $redis->subscribe(['realtime'], function (Redis $redis, string $channel, string $payload) use ($server, $workerId, $stats, $register, $realtime) {
$event = json_decode($payload, true);
if ($event['permissionsChanged'] && isset($event['userId'])) {
@@ -301,24 +384,25 @@ $server->onWorkerStart(function (int $workerId) use ($server, $container, $stats
if ($realtime->hasSubscriber($projectId, 'user:' . $userId)) {
$connection = array_key_first(reset($realtime->subscriptions[$projectId]['user:' . $userId]));
- $consoleDatabase = $container->get('dbForConsole');
-
- $project = $authorization->skip(fn () => $consoleDatabase->getDocument('projects', $projectId));
- $database = $container->get('getProjectDB')($project);
+ $consoleDatabase = getConsoleDB();
+ $project = Authorization::skip(fn () => $consoleDatabase->getDocument('projects', $projectId));
+ $database = getProjectDB($project);
$user = $database->getDocument('users', $userId);
- $roles = Auth::getRoles($user, $authorization);
+ $roles = Auth::getRoles($user);
$channels = $realtime->connections[$connection]['channels'];
$realtime->unsubscribe($connection);
$realtime->subscribe($projectId, $connection, $roles, $channels);
+
+ $register->get('pools')->reclaim();
}
}
$receivers = $realtime->getSubscribers($event);
- if (Http::isDevelopment() && !empty($receivers)) {
+ if (App::isDevelopment() && !empty($receivers)) {
Console::log("[Debug][Worker {$workerId}] Receivers: " . count($receivers));
Console::log("[Debug][Worker {$workerId}] Receivers Connection IDs: " . json_encode($receivers));
Console::log("[Debug][Worker {$workerId}] Event: " . $payload);
@@ -344,40 +428,30 @@ $server->onWorkerStart(function (int $workerId) use ($server, $container, $stats
sleep(DATABASE_RECONNECT_SLEEP);
continue;
} finally {
- ($container->get('connections'))->reclaim();
- $container->refresh('dbForConsole');
+ $register->get('pools')->reclaim();
}
}
Console::error('Failed to restart pub/sub...');
});
-$server->onOpen(function (int $connection, SwooleRequest $request) use ($server, $container, $stats, &$realtime, $logError) {
- $authorization = $container->get('authorization');
-
- $request = new Request(new UtopiaRequest($request));
- $response = new Response(new UtopiaResponse(new SwooleResponse()));
-
- $requestInjection = new Dependency();
- $responseInjection = new Dependency();
-
- $requestInjection->setName('request')->setCallback(fn () => $request);
- $responseInjection->setName('response')->setCallback(fn () => $response);
-
- $container->set($requestInjection);
- $container->set($responseInjection);
+$server->onOpen(function (int $connection, SwooleRequest $request) use ($server, $register, $stats, &$realtime, $logError) {
+ $app = new App('UTC');
+ $request = new Request($request);
+ $response = new Response(new SwooleResponse());
Console::info("Connection open (user: {$connection})");
+ App::setResource('pools', fn () => $register->get('pools'));
+ App::setResource('request', fn () => $request);
+ App::setResource('response', fn () => $response);
+
try {
-
/** @var Document $project */
- $project = $container->refresh('project')->get('project');
-
- $container->refresh('dbForProject');
+ $project = $app->getResource('project');
/*
- * Project Check
+ * Project Check
*/
if (empty($project->getId())) {
throw new Exception(Exception::REALTIME_POLICY_VIOLATION, 'Missing or unknown project ID');
@@ -386,16 +460,15 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
if (
array_key_exists('realtime', $project->getAttribute('apis', []))
&& !$project->getAttribute('apis', [])['realtime']
- && !(Auth::isPrivilegedUser($authorization->getRoles()) || Auth::isAppUser($authorization->getRoles()))
+ && !(Auth::isPrivilegedUser(Authorization::getRoles()) || Auth::isAppUser(Authorization::getRoles()))
) {
throw new AppwriteException(AppwriteException::GENERAL_API_DISABLED);
}
- $dbForProject = $container->get('getProjectDB')($project);
- /** @var Document $console */
- $console = $container->get('console');
- /** @var Document $user */
- $user = $container->refresh('user')->get('user');
+ $dbForProject = getProjectDB($project);
+ $console = $app->getResource('console'); /** @var Document $console */
+ $user = $app->getResource('user'); /** @var Document $user */
+
/*
* Abuse Check
*
@@ -424,8 +497,7 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
throw new Exception(Exception::REALTIME_POLICY_VIOLATION, $originValidator->getDescription());
}
- $authorization = $container->get('authorization');
- $roles = Auth::getRoles($user, $authorization);
+ $roles = Auth::getRoles($user);
$channels = Realtime::convertChannels($request->getQuery('channels', []), $user->getId());
@@ -474,33 +546,25 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
$server->send([$connection], json_encode($response));
$server->close($connection, $code);
- if (Http::isDevelopment()) {
+ if (App::isDevelopment()) {
Console::error('[Error] Connection Error');
Console::error('[Error] Code: ' . $response['data']['code']);
Console::error('[Error] Message: ' . $response['data']['message']);
}
} finally {
- $connections = $container->get('connections');
- $connections->reclaim();
+ $register->get('pools')->reclaim();
}
});
-$server->onWorkerStop(function (int $workerId) use ($container) {
- $connections = $container->get('connections');
- $connections->reclaim();
-});
-
-$server->onMessage(function (int $connection, string $message) use ($server, $container, $realtime, $containerId) {
+$server->onMessage(function (int $connection, string $message) use ($server, $register, $realtime, $containerId) {
try {
- $response = new Response(new HttpResponse(new SwooleHttpResponse()));
+ $response = new Response(new SwooleResponse());
$projectId = $realtime->connections[$connection]['projectId'];
- $database = $container->get('dbForConsole');
- $authorization = $container->get('authorization');
- $authentication = $container->get('authentication');
+ $database = getConsoleDB();
if ($projectId !== 'console') {
- $project = $authorization->skip(fn () => $database->getDocument('projects', $projectId));
- $database = $container->get('getProjectDB')($project);
+ $project = Authorization::skip(fn () => $database->getDocument('projects', $projectId));
+ $database = getProjectDB($project);
} else {
$project = null;
}
@@ -538,21 +602,20 @@ $server->onMessage(function (int $connection, string $message) use ($server, $co
}
$session = Auth::decodeSession($message['data']['session']);
+ Auth::$unique = $session['id'] ?? '';
+ Auth::$secret = $session['secret'] ?? '';
- $authentication->setUnique($session['id'] ?? '');
- $authentication->setSecret($session['secret'] ?? '');
-
- $user = $database->getDocument('users', $authentication->getUnique());
+ $user = $database->getDocument('users', Auth::$unique);
if (
empty($user->getId()) // Check a document has been found in the DB
- || !Auth::sessionVerify($user->getAttribute('sessions', []), $authentication->getSecret()) // Validate user has valid login token
+ || !Auth::sessionVerify($user->getAttribute('sessions', []), Auth::$secret) // Validate user has valid login token
) {
// cookie not valid
throw new Exception(Exception::REALTIME_MESSAGE_FORMAT_INVALID, 'Session is not valid.');
}
- $roles = Auth::getRoles($user, $authorization);
+ $roles = Auth::getRoles($user);
$channels = Realtime::convertChannels(array_flip($realtime->connections[$connection]['channels']), $user->getId());
$realtime->subscribe($realtime->connections[$connection]['projectId'], $connection, $roles, $channels);
@@ -586,8 +649,7 @@ $server->onMessage(function (int $connection, string $message) use ($server, $co
$server->close($connection, $th->getCode());
}
} finally {
- ($container->get('connections'))->reclaim();
- $container->refresh('dbForConsole');
+ $register->get('pools')->reclaim();
}
});
diff --git a/app/views/install/compose.phtml b/app/views/install/compose.phtml
index fcd4ab2006..ad35135a6f 100644
--- a/app/views/install/compose.phtml
+++ b/app/views/install/compose.phtml
@@ -11,8 +11,7 @@ $httpsPort = $this->getParam('httpsPort', '');
$version = $this->getParam('version', '');
$organization = $this->getParam('organization', '');
$image = $this->getParam('image', '');
-?>
-services:
+?>services:
traefik:
image: traefik:2.11
container_name: appwrite-traefik
@@ -167,7 +166,7 @@ services:
appwrite-console:
<<: *x-logging
container_name: appwrite-console
- image: /console:5.0.11
+ image: /console:5.0.12
restart: unless-stopped
networks:
- appwrite
@@ -337,6 +336,9 @@ services:
- _APP_LOGGING_CONFIG
- _APP_EXECUTOR_SECRET
- _APP_EXECUTOR_HOST
+ - _APP_MAINTENANCE_RETENTION_ABUSE
+ - _APP_MAINTENANCE_RETENTION_AUDIT
+ - _APP_MAINTENANCE_RETENTION_EXECUTION
appwrite-worker-databases:
image: /:
@@ -529,6 +531,8 @@ services:
- _APP_SMTP_USERNAME
- _APP_SMTP_PASSWORD
- _APP_LOGGING_CONFIG
+ - _APP_DOMAIN
+ - _APP_OPTIONS_FORCE_HTTPS
appwrite-worker-messaging:
image: /:
@@ -677,6 +681,7 @@ services:
entrypoint: worker-usage-dump
<<: *x-logging
container_name: appwrite-worker-usage-dump
+ restart: unless-stopped
networks:
- appwrite
depends_on:
@@ -790,7 +795,7 @@ services:
<<: *x-logging
restart: unless-stopped
stop_signal: SIGINT
- image: openruntimes/executor:0.6.7
+ image: openruntimes/executor:0.6.11
networks:
- appwrite
- runtimes
@@ -848,7 +853,7 @@ services:
- MYSQL_USER=${_APP_DB_USER}
- MYSQL_PASSWORD=${_APP_DB_PASS}
- MARIADB_AUTO_UPGRADE=1
- command: 'mysqld --innodb-flush-method=fsync --max_connections=5000'
+ command: 'mysqld --innodb-flush-method=fsync'
redis:
image: redis:7.2.4-alpine
diff --git a/app/worker.php b/app/worker.php
index 80d027fbc2..2d59259284 100644
--- a/app/worker.php
+++ b/app/worker.php
@@ -2,107 +2,278 @@
require_once __DIR__ . '/init.php';
+use Appwrite\Event\Audit;
+use Appwrite\Event\Build;
+use Appwrite\Event\Certificate;
+use Appwrite\Event\Database as EventDatabase;
+use Appwrite\Event\Delete;
+use Appwrite\Event\Event;
+use Appwrite\Event\Func;
+use Appwrite\Event\Mail;
+use Appwrite\Event\Messaging;
+use Appwrite\Event\Migration;
+use Appwrite\Event\Usage;
use Appwrite\Event\UsageDump;
use Appwrite\Platform\Appwrite;
-use Appwrite\Utopia\Queue\Connections;
use Swoole\Runtime;
+use Utopia\App;
+use Utopia\Cache\Adapter\Sharding;
+use Utopia\Cache\Cache;
use Utopia\CLI\Console;
+use Utopia\Config\Config;
use Utopia\Database\Database;
use Utopia\Database\DateTime;
use Utopia\Database\Document;
use Utopia\Database\Validator\Authorization;
-use Utopia\DI\Dependency;
+use Utopia\DSN\DSN;
use Utopia\Logger\Log;
use Utopia\Logger\Logger;
use Utopia\Platform\Service;
+use Utopia\Pools\Group;
use Utopia\Queue\Connection;
use Utopia\Queue\Message;
-use Utopia\Queue\Worker;
-use Utopia\Storage\Device\Local;
+use Utopia\Queue\Server;
+use Utopia\Registry\Registry;
use Utopia\System\System;
-global $registry, $container;
-
+Authorization::disable();
Runtime::enableCoroutine(SWOOLE_HOOK_ALL);
-$project = new Dependency();
-$register = new Dependency();
-$dbForProject = new Dependency();
-$abuseRetention = new Dependency();
-$deviceForCache = new Dependency();
-$auditRetention = new Dependency();
-$queueForUsageDump = new Dependency();
-$executionRetention = new Dependency();
-$deviceForLocalFiles = new Dependency();
+Server::setResource('register', fn () => $register);
-$register
- ->setName('register')
- ->setCallback(fn () => $registry);
+Server::setResource('dbForConsole', function (Cache $cache, Registry $register) {
+ $pools = $register->get('pools');
+ $database = $pools
+ ->get('console')
+ ->pop()
+ ->getResource();
-$project
- ->setName('project')
- ->inject('message')
- ->inject('dbForConsole')
- ->setCallback(function (Message $message, Database $dbForConsole) {
- $payload = $message->getPayload() ?? [];
- $project = new Document($payload['project'] ?? []);
+ $adapter = new Database($database, $cache);
+ $adapter->setNamespace('_console');
- if ($project->getId() === 'console') {
- return $project;
+ return $adapter;
+}, ['cache', 'register']);
+
+Server::setResource('project', function (Message $message, Database $dbForConsole) {
+ $payload = $message->getPayload() ?? [];
+ $project = new Document($payload['project'] ?? []);
+
+ if ($project->getId() === 'console' || $project->isEmpty() || ! empty($project->getInternalId())) {
+ return $project;
+ }
+
+ return $dbForConsole->getDocument('projects', $project->getId());
+}, ['message', 'dbForConsole']);
+
+Server::setResource('dbForProject', function (Cache $cache, Registry $register, Message $message, Document $project, Database $dbForConsole) {
+ if ($project->isEmpty() || $project->getId() === 'console') {
+ return $dbForConsole;
+ }
+
+ $pools = $register->get('pools');
+
+ try {
+ $dsn = new DSN($project->getAttribute('database'));
+ } catch (\InvalidArgumentException) {
+ // TODO: Temporary until all projects are using shared tables
+ $dsn = new DSN('mysql://' . $project->getAttribute('database'));
+ }
+
+ $adapter = $pools
+ ->get($dsn->getHost())
+ ->pop()
+ ->getResource();
+
+ $database = new Database($adapter, $cache);
+
+ try {
+ $dsn = new DSN($project->getAttribute('database'));
+ } catch (\InvalidArgumentException) {
+ // TODO: Temporary until all projects are using shared tables
+ $dsn = new DSN('mysql://' . $project->getAttribute('database'));
+ }
+
+ if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) {
+ $database
+ ->setSharedTables(true)
+ ->setTenant($project->getInternalId())
+ ->setNamespace($dsn->getParam('namespace'));
+ } else {
+ $database
+ ->setSharedTables(false)
+ ->setTenant(null)
+ ->setNamespace('_' . $project->getInternalId());
+ }
+
+ return $database;
+}, ['cache', 'register', 'message', 'project', 'dbForConsole']);
+
+Server::setResource('getProjectDB', function (Group $pools, Database $dbForConsole, $cache) {
+ $databases = []; // TODO: @Meldiron This should probably be responsibility of utopia-php/pools
+
+ return function (Document $project) use ($pools, $dbForConsole, $cache, &$databases): Database {
+ if ($project->isEmpty() || $project->getId() === 'console') {
+ return $dbForConsole;
}
- return $dbForConsole->getDocument('projects', $project->getId());
- });
+ try {
+ $dsn = new DSN($project->getAttribute('database'));
+ } catch (\InvalidArgumentException) {
+ // TODO: Temporary until all projects are using shared tables
+ $dsn = new DSN('mysql://' . $project->getAttribute('database'));
+ }
-$abuseRetention
- ->setName('abuseRetention')
- ->setCallback(function () {
- return DateTime::addSeconds(new \DateTime(), -1 * System::getEnv('_APP_MAINTENANCE_RETENTION_ABUSE', 86400));
- });
+ if (isset($databases[$dsn->getHost()])) {
+ $database = $databases[$dsn->getHost()];
-$auditRetention
- ->setName('auditRetention')
- ->setCallback(function () {
- return DateTime::addSeconds(new \DateTime(), -1 * System::getEnv('_APP_MAINTENANCE_RETENTION_AUDIT', 1209600));
- });
+ if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) {
+ $database
+ ->setSharedTables(true)
+ ->setTenant($project->getInternalId())
+ ->setNamespace($dsn->getParam('namespace'));
+ } else {
+ $database
+ ->setSharedTables(false)
+ ->setTenant(null)
+ ->setNamespace('_' . $project->getInternalId());
+ }
-$executionRetention
- ->setName('executionRetention')
- ->setCallback(function () {
- return DateTime::addSeconds(new \DateTime(), -1 * System::getEnv('_APP_MAINTENANCE_RETENTION_EXECUTION', 1209600));
- });
+ return $database;
+ }
-$queueForUsageDump
- ->setName('queueForUsageDump')
- ->inject('queue')
- ->setCallback(function (Connection $queue) {
- return new UsageDump($queue);
- });
+ $dbAdapter = $pools
+ ->get($dsn->getHost())
+ ->pop()
+ ->getResource();
-$deviceForCache
- ->setName('deviceForCache')
- ->inject('project')
- ->setCallback(function (Document $project) {
- return getDevice(APP_STORAGE_CACHE . '/app-' . $project->getId());
- });
+ $database = new Database($dbAdapter, $cache);
-$deviceForLocalFiles
- ->setName('deviceForLocalFiles')
- ->inject('project')
- ->setCallback(function (Document $project) {
- return new Local(APP_STORAGE_UPLOADS . '/app-' . $project->getId());
- });
+ $databases[$dsn->getHost()] = $database;
-$container->set($project);
-$container->set($register);
-$container->set($dbForProject);
-$container->set($abuseRetention);
-$container->set($auditRetention);
-$container->set($deviceForCache);
-$container->set($queueForUsageDump);
-$container->set($executionRetention);
-$container->set($deviceForLocalFiles);
+ if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) {
+ $database
+ ->setSharedTables(true)
+ ->setTenant($project->getInternalId())
+ ->setNamespace($dsn->getParam('namespace'));
+ } else {
+ $database
+ ->setSharedTables(false)
+ ->setTenant(null)
+ ->setNamespace('_' . $project->getInternalId());
+ }
+ return $database;
+ };
+}, ['pools', 'dbForConsole', 'cache']);
+
+Server::setResource('abuseRetention', function () {
+ return DateTime::addSeconds(new \DateTime(), -1 * System::getEnv('_APP_MAINTENANCE_RETENTION_ABUSE', 86400));
+});
+
+Server::setResource('auditRetention', function () {
+ return DateTime::addSeconds(new \DateTime(), -1 * System::getEnv('_APP_MAINTENANCE_RETENTION_AUDIT', 1209600));
+});
+
+Server::setResource('executionRetention', function () {
+ return DateTime::addSeconds(new \DateTime(), -1 * System::getEnv('_APP_MAINTENANCE_RETENTION_EXECUTION', 1209600));
+});
+
+Server::setResource('cache', function (Registry $register) {
+ $pools = $register->get('pools');
+ $list = Config::getParam('pools-cache', []);
+ $adapters = [];
+
+ foreach ($list as $value) {
+ $adapters[] = $pools
+ ->get($value)
+ ->pop()
+ ->getResource()
+ ;
+ }
+
+ return new Cache(new Sharding($adapters));
+}, ['register']);
+
+Server::setResource('log', fn () => new Log());
+
+Server::setResource('queueForUsage', function (Connection $queue) {
+ return new Usage($queue);
+}, ['queue']);
+
+Server::setResource('queueForUsageDump', function (Connection $queue) {
+ return new UsageDump($queue);
+}, ['queue']);
+
+Server::setResource('queue', function (Group $pools) {
+ return $pools->get('queue')->pop()->getResource();
+}, ['pools']);
+
+Server::setResource('queueForDatabase', function (Connection $queue) {
+ return new EventDatabase($queue);
+}, ['queue']);
+
+Server::setResource('queueForMessaging', function (Connection $queue) {
+ return new Messaging($queue);
+}, ['queue']);
+
+Server::setResource('queueForMails', function (Connection $queue) {
+ return new Mail($queue);
+}, ['queue']);
+
+Server::setResource('queueForBuilds', function (Connection $queue) {
+ return new Build($queue);
+}, ['queue']);
+
+Server::setResource('queueForDeletes', function (Connection $queue) {
+ return new Delete($queue);
+}, ['queue']);
+
+Server::setResource('queueForEvents', function (Connection $queue) {
+ return new Event($queue);
+}, ['queue']);
+
+Server::setResource('queueForAudits', function (Connection $queue) {
+ return new Audit($queue);
+}, ['queue']);
+
+Server::setResource('queueForFunctions', function (Connection $queue) {
+ return new Func($queue);
+}, ['queue']);
+
+Server::setResource('queueForCertificates', function (Connection $queue) {
+ return new Certificate($queue);
+}, ['queue']);
+
+Server::setResource('queueForMigrations', function (Connection $queue) {
+ return new Migration($queue);
+}, ['queue']);
+
+Server::setResource('logger', function (Registry $register) {
+ return $register->get('logger');
+}, ['register']);
+
+Server::setResource('pools', function (Registry $register) {
+ return $register->get('pools');
+}, ['register']);
+
+Server::setResource('deviceForFunctions', function (Document $project) {
+ return getDevice(APP_STORAGE_FUNCTIONS . '/app-' . $project->getId());
+}, ['project']);
+
+Server::setResource('deviceForFiles', function (Document $project) {
+ return getDevice(APP_STORAGE_UPLOADS . '/app-' . $project->getId());
+}, ['project']);
+
+Server::setResource('deviceForBuilds', function (Document $project) {
+ return getDevice(APP_STORAGE_BUILDS . '/app-' . $project->getId());
+}, ['project']);
+
+Server::setResource('deviceForCache', function (Document $project) {
+ return getDevice(APP_STORAGE_CACHE . '/app-' . $project->getId());
+}, ['project']);
+
+
+$pools = $register->get('pools');
$platform = new Appwrite();
$args = $platform->getEnv('argv');
@@ -121,13 +292,6 @@ if (\str_starts_with($workerName, 'databases')) {
}
try {
- $connection = new Connection\Redis(
- System::getEnv('_APP_REDIS_HOST', 'redis'),
- System::getEnv('_APP_REDIS_PORT', '6379'),
- System::getEnv('_APP_REDIS_USER', ''),
- System::getEnv('_APP_REDIS_PASS', '')
- );
-
/**
* Any worker can be configured with the following env vars:
* - _APP_WORKERS_NUM The total number of worker processes
@@ -136,35 +300,32 @@ try {
*/
$platform->init(Service::TYPE_WORKER, [
'workersNum' => System::getEnv('_APP_WORKERS_NUM', 1),
- 'connection' => $connection,
+ 'connection' => $pools->get('queue')->pop()->getResource(),
'workerName' => strtolower($workerName) ?? null,
'queueName' => $queueName
]);
} catch (\Throwable $e) {
- Console::error($e->getMessage() . ', File: ' . $e->getFile() . ', Line: ' . $e->getLine());
+ Console::error($e->getMessage() . ', File: ' . $e->getFile() . ', Line: ' . $e->getLine());
}
-Worker::init()
- ->inject('authorization')
- ->action(function (Authorization $authorization) {
- $authorization->disable();
+$worker = $platform->getWorker();
+
+$worker
+ ->shutdown()
+ ->inject('pools')
+ ->action(function (Group $pools) {
+ $pools->reclaim();
});
-Worker::shutdown()
- ->inject('connections')
- ->action(function (Connections $connections) {
- $connections->reclaim();
- });
-
-Worker::error()
+$worker
+ ->error()
->inject('error')
->inject('logger')
->inject('log')
- ->inject('connections')
+ ->inject('pools')
->inject('project')
- ->inject('authorization')
- ->action(function (Throwable $error, ?Logger $logger, Log $log, Connections $connections, Document $project, Authorization $authorization) use ($queueName) {
- $connections->reclaim();
+ ->action(function (Throwable $error, ?Logger $logger, Log $log, Group $pools, Document $project) use ($queueName) {
+ $pools->reclaim();
$version = System::getEnv('_APP_VERSION', 'UNKNOWN');
if ($logger) {
@@ -180,13 +341,17 @@ Worker::error()
$log->addExtra('file', $error->getFile());
$log->addExtra('line', $error->getLine());
$log->addExtra('trace', $error->getTraceAsString());
- $log->addExtra('roles', $authorization->getRoles());
+ $log->addExtra('roles', Authorization::getRoles());
$isProduction = System::getEnv('_APP_ENV', 'development') === 'production';
$log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING);
- $responseCode = $logger->addLog($log);
- Console::info('Usage stats log pushed with status code: ' . $responseCode);
+ try {
+ $responseCode = $logger->addLog($log);
+ Console::info('Error log pushed with status code: ' . $responseCode);
+ } catch (Throwable $th) {
+ Console::error('Error pushing log: ' . $th->getMessage());
+ }
}
Console::error('[Error] Type: ' . get_class($error));
@@ -195,7 +360,9 @@ Worker::error()
Console::error('[Error] Line: ' . $error->getLine());
});
-$platform
- ->getWorker()
- ->setContainer($container)
- ->start();
+$worker->workerStart()
+ ->action(function () use ($workerName) {
+ Console::info("Worker $workerName started");
+ });
+
+$worker->start();
diff --git a/composer.json b/composer.json
index 479770ffbd..c176d383f3 100644
--- a/composer.json
+++ b/composer.json
@@ -4,7 +4,6 @@
"description": "End to end backend server for frontend and mobile apps.",
"type": "project",
"license": "BSD-3-Clause",
- "minimum-stability": "stable",
"authors": [
{
"name": "Eldad Fux",
@@ -15,8 +14,7 @@
"test": "vendor/bin/phpunit",
"lint": "vendor/bin/pint --test",
"format": "vendor/bin/pint",
- "bench": "vendor/bin/phpbench run --report=benchmark",
- "check": "./vendor/bin/phpstan analyse -c phpstan.neon --memory-limit 1G app src tests"
+ "bench": "vendor/bin/phpbench run --report=benchmark"
},
"autoload": {
"psr-4": {
@@ -47,33 +45,33 @@
"ext-sockets": "*",
"appwrite/php-runtimes": "0.15.*",
"appwrite/php-clamav": "2.0.*",
- "utopia-php/abuse": "0.45.*",
- "utopia-php/analytics": "0.13.*",
- "utopia-php/audit": "0.45.*",
+ "utopia-php/abuse": "0.43.0",
+ "utopia-php/analytics": "0.10.*",
+ "utopia-php/audit": "0.43.0",
"utopia-php/cache": "0.10.*",
- "utopia-php/cli": "0.19.*",
+ "utopia-php/cli": "0.15.*",
"utopia-php/config": "0.2.*",
- "utopia-php/database": "0.55.*",
- "utopia-php/domains": "0.6.*",
- "utopia-php/dsn": "0.2.*",
- "utopia-php/framework": "1.0.*",
+ "utopia-php/database": "0.53.5",
+ "utopia-php/domains": "0.5.*",
+ "utopia-php/dsn": "0.2.1",
+ "utopia-php/framework": "0.33.*",
"utopia-php/fetch": "0.2.*",
- "utopia-php/image": "0.6.*",
+ "utopia-php/image": "0.7.*",
"utopia-php/locale": "0.4.*",
"utopia-php/logger": "0.6.*",
"utopia-php/messaging": "0.12.*",
- "utopia-php/migration": "0.4.*",
- "utopia-php/orchestration": "0.15.*",
- "utopia-php/platform": "0.8.*",
- "utopia-php/view": "0.2.*",
+ "utopia-php/migration": "0.6.*",
+ "utopia-php/orchestration": "0.9.*",
+ "utopia-php/platform": "0.7.*",
"utopia-php/pools": "0.5.*",
"utopia-php/preloader": "0.2.*",
- "utopia-php/queue": "0.8.*",
+ "utopia-php/queue": "0.7.*",
"utopia-php/registry": "0.5.*",
- "utopia-php/storage": "0.19.*",
+ "utopia-php/storage": "0.18.*",
+ "utopia-php/swoole": "0.8.*",
"utopia-php/system": "0.8.*",
- "utopia-php/vcs": "0.9.*",
- "utopia-php/websocket": "0.2.*",
+ "utopia-php/vcs": "0.8.*",
+ "utopia-php/websocket": "0.1.*",
"matomo/device-detector": "6.1.*",
"dragonmantank/cron-expression": "3.3.2",
"phpmailer/phpmailer": "6.9.1",
@@ -90,8 +88,7 @@
"swoole/ide-helper": "5.1.2",
"textalk/websocket": "1.5.7",
"laravel/pint": "^1.14",
- "phpbench/phpbench": "^1.2",
- "phpstan/phpstan": "1.8.*"
+ "phpbench/phpbench": "^1.2"
},
"provide": {
"ext-phpiredis": "*"
diff --git a/composer.lock b/composer.lock
index ee5e483d1f..4281d45493 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "2244ee2e6eb7e953f40be5e58d4dd7ff",
+ "content-hash": "1884e3a2966762c4a955842426b64f6c",
"packages": [
{
"name": "adhocore/jwt",
@@ -65,16 +65,16 @@
},
{
"name": "appwrite/appwrite",
- "version": "10.1.0",
+ "version": "11.1.0",
"source": {
"type": "git",
"url": "https://github.com/appwrite/sdk-for-php.git",
- "reference": "da579af70723cfc117b5af84375bdef117e27312"
+ "reference": "1d043f543acdb17b9fdb440b1b2dd208e400bad3"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/appwrite/sdk-for-php/zipball/da579af70723cfc117b5af84375bdef117e27312",
- "reference": "da579af70723cfc117b5af84375bdef117e27312",
+ "url": "https://api.github.com/repos/appwrite/sdk-for-php/zipball/1d043f543acdb17b9fdb440b1b2dd208e400bad3",
+ "reference": "1d043f543acdb17b9fdb440b1b2dd208e400bad3",
"shasum": ""
},
"require": {
@@ -83,7 +83,8 @@
"php": ">=7.1.0"
},
"require-dev": {
- "phpunit/phpunit": "3.7.35"
+ "mockery/mockery": "^1.6.6",
+ "phpunit/phpunit": "^10"
},
"type": "library",
"autoload": {
@@ -99,10 +100,10 @@
"support": {
"email": "team@appwrite.io",
"issues": "https://github.com/appwrite/sdk-for-php/issues",
- "source": "https://github.com/appwrite/sdk-for-php/tree/10.1.0",
+ "source": "https://github.com/appwrite/sdk-for-php/tree/11.1.0",
"url": "https://appwrite.io/support"
},
- "time": "2023-11-20T09:56:12+00:00"
+ "time": "2024-06-26T07:03:23+00:00"
},
{
"name": "appwrite/php-clamav",
@@ -1429,16 +1430,16 @@
},
{
"name": "utopia-php/abuse",
- "version": "0.45.0",
+ "version": "0.43.0",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/abuse.git",
- "reference": "9e5edb302db9aa88279272de00271d3cebcb3c7a"
+ "reference": "6346a3b4c5177a43160035a7289e30fdfb0790d6"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/utopia-php/abuse/zipball/9e5edb302db9aa88279272de00271d3cebcb3c7a",
- "reference": "9e5edb302db9aa88279272de00271d3cebcb3c7a",
+ "url": "https://api.github.com/repos/utopia-php/abuse/zipball/6346a3b4c5177a43160035a7289e30fdfb0790d6",
+ "reference": "6346a3b4c5177a43160035a7289e30fdfb0790d6",
"shasum": ""
},
"require": {
@@ -1446,7 +1447,7 @@
"ext-pdo": "*",
"ext-redis": "*",
"php": ">=8.0",
- "utopia-php/database": "0.55.*"
+ "utopia-php/database": "0.53.*"
},
"require-dev": {
"laravel/pint": "1.5.*",
@@ -1474,28 +1475,27 @@
],
"support": {
"issues": "https://github.com/utopia-php/abuse/issues",
- "source": "https://github.com/utopia-php/abuse/tree/0.45.0"
+ "source": "https://github.com/utopia-php/abuse/tree/0.43.0"
},
- "time": "2024-09-18T04:39:25+00:00"
+ "time": "2024-08-30T05:17:23+00:00"
},
{
"name": "utopia-php/analytics",
- "version": "0.13.0",
+ "version": "0.10.2",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/analytics.git",
- "reference": "3dace02af5d4190623f88fb6e02f5559a99f14c4"
+ "reference": "14c805114736f44c26d6d24b176a2f8b93d86a1f"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/utopia-php/analytics/zipball/3dace02af5d4190623f88fb6e02f5559a99f14c4",
- "reference": "3dace02af5d4190623f88fb6e02f5559a99f14c4",
+ "url": "https://api.github.com/repos/utopia-php/analytics/zipball/14c805114736f44c26d6d24b176a2f8b93d86a1f",
+ "reference": "14c805114736f44c26d6d24b176a2f8b93d86a1f",
"shasum": ""
},
"require": {
"php": ">=8.0",
- "utopia-php/cli": "0.19.*",
- "utopia-php/system": "0.8.*"
+ "utopia-php/cli": "^0.15.0"
},
"require-dev": {
"laravel/pint": "dev-main",
@@ -1521,27 +1521,27 @@
],
"support": {
"issues": "https://github.com/utopia-php/analytics/issues",
- "source": "https://github.com/utopia-php/analytics/tree/0.13.0"
+ "source": "https://github.com/utopia-php/analytics/tree/0.10.2"
},
- "time": "2024-09-05T16:19:26+00:00"
+ "time": "2023-03-22T12:01:09+00:00"
},
{
"name": "utopia-php/audit",
- "version": "0.45.0",
+ "version": "0.43.0",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/audit.git",
- "reference": "7269c5378fcc36d2c3d07cb98bea160236aab805"
+ "reference": "cef22b5dc6a6d28fcd522f41c7bf7ded4a4dfd3e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/utopia-php/audit/zipball/7269c5378fcc36d2c3d07cb98bea160236aab805",
- "reference": "7269c5378fcc36d2c3d07cb98bea160236aab805",
+ "url": "https://api.github.com/repos/utopia-php/audit/zipball/cef22b5dc6a6d28fcd522f41c7bf7ded4a4dfd3e",
+ "reference": "cef22b5dc6a6d28fcd522f41c7bf7ded4a4dfd3e",
"shasum": ""
},
"require": {
"php": ">=8.0",
- "utopia-php/database": "0.55.*"
+ "utopia-php/database": "0.53.*"
},
"require-dev": {
"laravel/pint": "1.5.*",
@@ -1568,9 +1568,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/audit/issues",
- "source": "https://github.com/utopia-php/audit/tree/0.45.0"
+ "source": "https://github.com/utopia-php/audit/tree/0.43.0"
},
- "time": "2024-09-18T04:40:00+00:00"
+ "time": "2024-08-30T05:17:36+00:00"
},
{
"name": "utopia-php/cache",
@@ -1624,29 +1624,27 @@
},
{
"name": "utopia-php/cli",
- "version": "0.19.0",
+ "version": "0.15.1",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/cli.git",
- "reference": "f8af1d6087f498bc1f0191750a118d357ded9948"
+ "reference": "d69bbe51a6a94dc4e5bcdd542b5938038b985a65"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/utopia-php/cli/zipball/f8af1d6087f498bc1f0191750a118d357ded9948",
- "reference": "f8af1d6087f498bc1f0191750a118d357ded9948",
+ "url": "https://api.github.com/repos/utopia-php/cli/zipball/d69bbe51a6a94dc4e5bcdd542b5938038b985a65",
+ "reference": "d69bbe51a6a94dc4e5bcdd542b5938038b985a65",
"shasum": ""
},
"require": {
"php": ">=7.4",
- "utopia-php/di": "0.1.*",
- "utopia-php/framework": "1.0.*"
+ "utopia-php/framework": "0.*.*"
},
"require-dev": {
"laravel/pint": "1.2.*",
- "phpstan/phpstan": "^1.10",
"phpunit/phpunit": "^9.3",
"squizlabs/php_codesniffer": "^3.6",
- "swoole/ide-helper": "4.8.8"
+ "vimeo/psalm": "4.0.1"
},
"type": "library",
"autoload": {
@@ -1669,9 +1667,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/cli/issues",
- "source": "https://github.com/utopia-php/cli/tree/0.19.0"
+ "source": "https://github.com/utopia-php/cli/tree/0.15.1"
},
- "time": "2024-09-05T15:46:56+00:00"
+ "time": "2024-10-04T13:55:36+00:00"
},
{
"name": "utopia-php/config",
@@ -1726,16 +1724,16 @@
},
{
"name": "utopia-php/database",
- "version": "0.55",
+ "version": "0.53.5",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/database.git",
- "reference": "0ad2eb1c163196cd79253c59f1896c5b03d6ba91"
+ "reference": "689ba22063bf46def385da8695ba7a921e81e38d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/utopia-php/database/zipball/0ad2eb1c163196cd79253c59f1896c5b03d6ba91",
- "reference": "0ad2eb1c163196cd79253c59f1896c5b03d6ba91",
+ "url": "https://api.github.com/repos/utopia-php/database/zipball/689ba22063bf46def385da8695ba7a921e81e38d",
+ "reference": "689ba22063bf46def385da8695ba7a921e81e38d",
"shasum": ""
},
"require": {
@@ -1743,7 +1741,7 @@
"ext-pdo": "*",
"php": ">=8.0",
"utopia-php/cache": "0.10.*",
- "utopia-php/framework": "1.0.*",
+ "utopia-php/framework": "0.33.*",
"utopia-php/mongo": "0.3.*"
},
"require-dev": {
@@ -1754,7 +1752,7 @@
"phpunit/phpunit": "9.6.*",
"rregeer/phpunit-coverage-check": "0.3.*",
"swoole/ide-helper": "5.1.3",
- "utopia-php/cli": "0.19.*"
+ "utopia-php/cli": "0.14.*"
},
"type": "library",
"autoload": {
@@ -1776,79 +1774,30 @@
],
"support": {
"issues": "https://github.com/utopia-php/database/issues",
- "source": "https://github.com/utopia-php/database/tree/0.55"
+ "source": "https://github.com/utopia-php/database/tree/0.53.5"
},
- "time": "2024-09-18T04:00:12+00:00"
- },
- {
- "name": "utopia-php/di",
- "version": "0.1.0",
- "source": {
- "type": "git",
- "url": "https://github.com/utopia-php/di.git",
- "reference": "22490c95f7ac3898ed1c33f1b1b5dd577305ee31"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/utopia-php/di/zipball/22490c95f7ac3898ed1c33f1b1b5dd577305ee31",
- "reference": "22490c95f7ac3898ed1c33f1b1b5dd577305ee31",
- "shasum": ""
- },
- "require": {
- "php": ">=8.2"
- },
- "require-dev": {
- "laravel/pint": "^1.2",
- "phpbench/phpbench": "^1.2",
- "phpstan/phpstan": "^1.10",
- "phpunit/phpunit": "^9.5.25",
- "swoole/ide-helper": "4.8.3"
- },
- "type": "library",
- "autoload": {
- "psr-4": {
- "Utopia\\": "src/",
- "Tests\\E2E\\": "tests/e2e"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "description": "A simple and lite library for managing dependency injections",
- "keywords": [
- "framework",
- "http",
- "php",
- "upf"
- ],
- "support": {
- "issues": "https://github.com/utopia-php/di/issues",
- "source": "https://github.com/utopia-php/di/tree/0.1.0"
- },
- "time": "2024-08-08T14:35:19+00:00"
+ "time": "2024-09-24T08:43:10+00:00"
},
{
"name": "utopia-php/domains",
- "version": "0.6.0",
+ "version": "0.5.0",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/domains.git",
- "reference": "5c70b0f524deeb1fccc3962ad1da650ae2c94cda"
+ "reference": "bf07f60326f8389f378ddf6fcde86217e5cfe18c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/utopia-php/domains/zipball/5c70b0f524deeb1fccc3962ad1da650ae2c94cda",
- "reference": "5c70b0f524deeb1fccc3962ad1da650ae2c94cda",
+ "url": "https://api.github.com/repos/utopia-php/domains/zipball/bf07f60326f8389f378ddf6fcde86217e5cfe18c",
+ "reference": "bf07f60326f8389f378ddf6fcde86217e5cfe18c",
"shasum": ""
},
"require": {
"php": ">=8.0",
- "utopia-php/framework": "1.0.*"
+ "utopia-php/framework": "0.*.*"
},
"require-dev": {
"laravel/pint": "1.2.*",
- "phpstan/phpstan": "1.9.x-dev",
"phpunit/phpunit": "^9.3"
},
"type": "library",
@@ -1885,9 +1834,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/domains/issues",
- "source": "https://github.com/utopia-php/domains/tree/0.6.0"
+ "source": "https://github.com/utopia-php/domains/tree/0.5.0"
},
- "time": "2024-09-05T16:21:11+00:00"
+ "time": "2024-01-03T22:04:27+00:00"
},
{
"name": "utopia-php/dsn",
@@ -1977,30 +1926,26 @@
},
{
"name": "utopia-php/framework",
- "version": "1.0.2",
+ "version": "0.33.8",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/http.git",
- "reference": "fc63ec61c720190a5ea5bb484c615145850951e6"
+ "reference": "a7f577540a25cb90896fef2b64767bf8d700f3c5"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/utopia-php/http/zipball/fc63ec61c720190a5ea5bb484c615145850951e6",
- "reference": "fc63ec61c720190a5ea5bb484c615145850951e6",
+ "url": "https://api.github.com/repos/utopia-php/http/zipball/a7f577540a25cb90896fef2b64767bf8d700f3c5",
+ "reference": "a7f577540a25cb90896fef2b64767bf8d700f3c5",
"shasum": ""
},
"require": {
- "ext-swoole": "*",
- "php": ">=8.0",
- "utopia-php/servers": "0.1.*"
+ "php": ">=8.0"
},
"require-dev": {
- "ext-xdebug": "*",
"laravel/pint": "^1.2",
"phpbench/phpbench": "^1.2",
"phpstan/phpstan": "^1.10",
- "phpunit/phpunit": "^9.5.25",
- "swoole/ide-helper": "4.8.3"
+ "phpunit/phpunit": "^9.5.25"
},
"type": "library",
"autoload": {
@@ -2012,36 +1957,35 @@
"license": [
"MIT"
],
- "description": "A simple, light and advanced PHP HTTP framework",
+ "description": "A simple, light and advanced PHP framework",
"keywords": [
"framework",
- "http",
"php",
"upf"
],
"support": {
"issues": "https://github.com/utopia-php/http/issues",
- "source": "https://github.com/utopia-php/http/tree/1.0.2"
+ "source": "https://github.com/utopia-php/http/tree/0.33.8"
},
- "time": "2024-09-10T09:04:19+00:00"
+ "time": "2024-08-15T14:10:09+00:00"
},
{
"name": "utopia-php/image",
- "version": "0.6.1",
+ "version": "0.7.0",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/image.git",
- "reference": "2d74c27e69e65a93cf94a16586598a04fe435bf0"
+ "reference": "fcea143edbad524bf871ddbebe801d981f91f181"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/utopia-php/image/zipball/2d74c27e69e65a93cf94a16586598a04fe435bf0",
- "reference": "2d74c27e69e65a93cf94a16586598a04fe435bf0",
+ "url": "https://api.github.com/repos/utopia-php/image/zipball/fcea143edbad524bf871ddbebe801d981f91f181",
+ "reference": "fcea143edbad524bf871ddbebe801d981f91f181",
"shasum": ""
},
"require": {
"ext-imagick": "*",
- "php": ">=8.0"
+ "php": ">=8.1"
},
"require-dev": {
"laravel/pint": "1.2.*",
@@ -2069,9 +2013,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/image/issues",
- "source": "https://github.com/utopia-php/image/tree/0.6.1"
+ "source": "https://github.com/utopia-php/image/tree/0.7.0"
},
- "time": "2024-02-05T13:31:44+00:00"
+ "time": "2024-10-02T05:45:38+00:00"
},
{
"name": "utopia-php/locale",
@@ -2231,26 +2175,35 @@
},
{
"name": "utopia-php/migration",
- "version": "0.4.4",
+ "version": "0.6.4",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/migration.git",
- "reference": "a8a5d392bebf082faf289f4dfe09d9fd76844c33"
+ "reference": "e43ef283f1386084e11d1ffe093fb6c6d7a6ce6c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/utopia-php/migration/zipball/a8a5d392bebf082faf289f4dfe09d9fd76844c33",
- "reference": "a8a5d392bebf082faf289f4dfe09d9fd76844c33",
+ "url": "https://api.github.com/repos/utopia-php/migration/zipball/e43ef283f1386084e11d1ffe093fb6c6d7a6ce6c",
+ "reference": "e43ef283f1386084e11d1ffe093fb6c6d7a6ce6c",
"shasum": ""
},
"require": {
- "appwrite/appwrite": "10.1.0",
- "php": "8.*"
+ "appwrite/appwrite": "11.1.*",
+ "ext-curl": "*",
+ "ext-openssl": "*",
+ "php": "8.3.*",
+ "utopia-php/database": "0.53.*",
+ "utopia-php/dsn": "0.2.*",
+ "utopia-php/framework": "0.33.*",
+ "utopia-php/storage": "0.18.*"
},
"require-dev": {
- "laravel/pint": "1.*",
- "phpunit/phpunit": "9.*",
- "vlucas/phpdotenv": "5.*"
+ "ext-pdo": "*",
+ "laravel/pint": "1.17.*",
+ "phpstan/phpstan": "1.11.*",
+ "phpunit/phpunit": "11.2.*",
+ "utopia-php/cli": "0.16.*",
+ "vlucas/phpdotenv": "5.6.*"
},
"type": "library",
"autoload": {
@@ -2272,9 +2225,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/migration/issues",
- "source": "https://github.com/utopia-php/migration/tree/0.4.4"
+ "source": "https://github.com/utopia-php/migration/tree/0.6.4"
},
- "time": "2024-05-17T05:25:31+00:00"
+ "time": "2024-10-02T15:16:36+00:00"
},
{
"name": "utopia-php/mongo",
@@ -2338,26 +2291,26 @@
},
{
"name": "utopia-php/orchestration",
- "version": "0.15.0",
+ "version": "0.9.1",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/orchestration.git",
- "reference": "cd55650ba5f13118c3580048e6dd86b604f9a5b3"
+ "reference": "55f43513b3f940a3f4f9c2cde7682d0c2581beb0"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/utopia-php/orchestration/zipball/cd55650ba5f13118c3580048e6dd86b604f9a5b3",
- "reference": "cd55650ba5f13118c3580048e6dd86b604f9a5b3",
+ "url": "https://api.github.com/repos/utopia-php/orchestration/zipball/55f43513b3f940a3f4f9c2cde7682d0c2581beb0",
+ "reference": "55f43513b3f940a3f4f9c2cde7682d0c2581beb0",
"shasum": ""
},
"require": {
"php": ">=8.0",
- "utopia-php/cli": "0.19.*"
+ "utopia-php/cli": "0.15.*"
},
"require-dev": {
"laravel/pint": "^1.2",
- "phpstan/phpstan": "^1.10",
- "phpunit/phpunit": "^9.3"
+ "phpunit/phpunit": "^9.3",
+ "vimeo/psalm": "4.0.1"
},
"type": "library",
"autoload": {
@@ -2382,32 +2335,31 @@
],
"support": {
"issues": "https://github.com/utopia-php/orchestration/issues",
- "source": "https://github.com/utopia-php/orchestration/tree/0.15.0"
+ "source": "https://github.com/utopia-php/orchestration/tree/0.9.1"
},
- "time": "2024-09-05T16:28:02+00:00"
+ "time": "2023-03-17T15:05:06+00:00"
},
{
"name": "utopia-php/platform",
- "version": "0.8.1",
+ "version": "0.7.0",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/platform.git",
- "reference": "95d57f38a4001c7189a66885c485ac635d305234"
+ "reference": "beeea0f2c9bce14a6869fc5c87a1047cdecb5c52"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/utopia-php/platform/zipball/95d57f38a4001c7189a66885c485ac635d305234",
- "reference": "95d57f38a4001c7189a66885c485ac635d305234",
+ "url": "https://api.github.com/repos/utopia-php/platform/zipball/beeea0f2c9bce14a6869fc5c87a1047cdecb5c52",
+ "reference": "beeea0f2c9bce14a6869fc5c87a1047cdecb5c52",
"shasum": ""
},
"require": {
"ext-json": "*",
"ext-redis": "*",
"php": ">=8.0",
- "utopia-php/cli": "0.19.*",
- "utopia-php/framework": "1.0.*",
- "utopia-php/queue": "0.8.*",
- "utopia-php/servers": "0.1.*"
+ "utopia-php/cli": "0.15.*",
+ "utopia-php/framework": "0.33.*",
+ "utopia-php/queue": "0.7.*"
},
"require-dev": {
"laravel/pint": "1.2.*",
@@ -2433,9 +2385,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/platform/issues",
- "source": "https://github.com/utopia-php/platform/tree/0.8.1"
+ "source": "https://github.com/utopia-php/platform/tree/0.7.0"
},
- "time": "2024-09-06T02:33:27+00:00"
+ "time": "2024-05-08T17:00:55+00:00"
},
{
"name": "utopia-php/pools",
@@ -2543,23 +2495,22 @@
},
{
"name": "utopia-php/queue",
- "version": "0.8.0",
+ "version": "0.7.0",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/queue.git",
- "reference": "a518b271f8c158d6e66e36972f767189111033c2"
+ "reference": "917565256eb94bcab7246f7a746b1a486813761b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/utopia-php/queue/zipball/a518b271f8c158d6e66e36972f767189111033c2",
- "reference": "a518b271f8c158d6e66e36972f767189111033c2",
+ "url": "https://api.github.com/repos/utopia-php/queue/zipball/917565256eb94bcab7246f7a746b1a486813761b",
+ "reference": "917565256eb94bcab7246f7a746b1a486813761b",
"shasum": ""
},
"require": {
"php": ">=8.0",
- "utopia-php/cli": "0.19.*",
- "utopia-php/di": "0.1.*",
- "utopia-php/servers": "0.1.*"
+ "utopia-php/cli": "0.15.*",
+ "utopia-php/framework": "0.*.*"
},
"require-dev": {
"laravel/pint": "^0.2.3",
@@ -2569,7 +2520,6 @@
"workerman/workerman": "^4.0"
},
"suggest": {
- "ext-redis": "Needed to support Redis connections",
"ext-swoole": "Needed to support Swoole.",
"workerman/workerman": "Needed to support Workerman."
},
@@ -2600,9 +2550,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/queue/issues",
- "source": "https://github.com/utopia-php/queue/tree/0.8.0"
+ "source": "https://github.com/utopia-php/queue/tree/0.7.0"
},
- "time": "2024-09-05T16:33:01+00:00"
+ "time": "2024-01-17T19:00:43+00:00"
},
{
"name": "utopia-php/registry",
@@ -2656,71 +2606,18 @@
},
"time": "2021-03-10T10:45:22+00:00"
},
- {
- "name": "utopia-php/servers",
- "version": "0.1.1",
- "source": {
- "type": "git",
- "url": "https://github.com/utopia-php/servers.git",
- "reference": "fd5c8d32778f265256c1936372a071b944f5ba8a"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/utopia-php/servers/zipball/fd5c8d32778f265256c1936372a071b944f5ba8a",
- "reference": "fd5c8d32778f265256c1936372a071b944f5ba8a",
- "shasum": ""
- },
- "require": {
- "php": ">=8.0",
- "utopia-php/di": "0.1.*"
- },
- "require-dev": {
- "laravel/pint": "^0.2.3",
- "phpstan/phpstan": "^1.8",
- "phpunit/phpunit": "^9.5.5"
- },
- "type": "library",
- "autoload": {
- "psr-4": {
- "Utopia\\Servers\\": "src/Servers"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Team Appwrite",
- "email": "team@appwrite.io"
- }
- ],
- "description": "A base library for building Utopia style servers.",
- "keywords": [
- "framework",
- "php",
- "servers",
- "upf",
- "utopia"
- ],
- "support": {
- "issues": "https://github.com/utopia-php/servers/issues",
- "source": "https://github.com/utopia-php/servers/tree/0.1.1"
- },
- "time": "2024-09-06T02:25:56+00:00"
- },
{
"name": "utopia-php/storage",
- "version": "0.19.0",
+ "version": "0.18.5",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/storage.git",
- "reference": "5013b894a776874d6010753fc9349df2225c69af"
+ "reference": "7d355c5e3ccc8ecebc0266f8ddd30088a43be919"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/utopia-php/storage/zipball/5013b894a776874d6010753fc9349df2225c69af",
- "reference": "5013b894a776874d6010753fc9349df2225c69af",
+ "url": "https://api.github.com/repos/utopia-php/storage/zipball/7d355c5e3ccc8ecebc0266f8ddd30088a43be919",
+ "reference": "7d355c5e3ccc8ecebc0266f8ddd30088a43be919",
"shasum": ""
},
"require": {
@@ -2732,8 +2629,8 @@
"ext-zlib": "*",
"ext-zstd": "*",
"php": ">=8.0",
- "utopia-php/framework": "1.0.*",
- "utopia-php/system": "0.8.*"
+ "utopia-php/framework": "0.*.*",
+ "utopia-php/system": "0.*.*"
},
"require-dev": {
"laravel/pint": "1.2.*",
@@ -2760,9 +2657,60 @@
],
"support": {
"issues": "https://github.com/utopia-php/storage/issues",
- "source": "https://github.com/utopia-php/storage/tree/0.19.0"
+ "source": "https://github.com/utopia-php/storage/tree/0.18.5"
},
- "time": "2024-09-05T17:00:24+00:00"
+ "time": "2024-09-04T08:57:27+00:00"
+ },
+ {
+ "name": "utopia-php/swoole",
+ "version": "0.8.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/utopia-php/swoole.git",
+ "reference": "5fa9d42c608ad46a4ce42a6d2b2eae00592fccd4"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/utopia-php/swoole/zipball/5fa9d42c608ad46a4ce42a6d2b2eae00592fccd4",
+ "reference": "5fa9d42c608ad46a4ce42a6d2b2eae00592fccd4",
+ "shasum": ""
+ },
+ "require": {
+ "ext-swoole": "*",
+ "php": ">=8.0",
+ "utopia-php/framework": "0.33.*"
+ },
+ "require-dev": {
+ "laravel/pint": "1.2.*",
+ "phpstan/phpstan": "^1.10",
+ "phpunit/phpunit": "^9.3",
+ "swoole/ide-helper": "5.0.2"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Utopia\\Swoole\\": "src/Swoole"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "An extension for Utopia Framework to work with PHP Swoole as a PHP FPM alternative",
+ "keywords": [
+ "framework",
+ "http",
+ "php",
+ "server",
+ "swoole",
+ "upf",
+ "utopia"
+ ],
+ "support": {
+ "issues": "https://github.com/utopia-php/swoole/issues",
+ "source": "https://github.com/utopia-php/swoole/tree/0.8.2"
+ },
+ "time": "2024-02-01T14:54:12+00:00"
},
{
"name": "utopia-php/system",
@@ -2822,24 +2770,23 @@
},
{
"name": "utopia-php/vcs",
- "version": "0.9.0",
+ "version": "0.8.2",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/vcs.git",
- "reference": "673abe2fef0750a841a4fa8fa6f99d4a602c68e7"
+ "reference": "eb9b7eade1a46a4f660e0d5a6304f7fa26ec9d18"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/utopia-php/vcs/zipball/673abe2fef0750a841a4fa8fa6f99d4a602c68e7",
- "reference": "673abe2fef0750a841a4fa8fa6f99d4a602c68e7",
+ "url": "https://api.github.com/repos/utopia-php/vcs/zipball/eb9b7eade1a46a4f660e0d5a6304f7fa26ec9d18",
+ "reference": "eb9b7eade1a46a4f660e0d5a6304f7fa26ec9d18",
"shasum": ""
},
"require": {
"adhocore/jwt": "^1.1",
"php": ">=8.0",
- "utopia-php/cache": "0.10.*",
- "utopia-php/framework": "1.0.*",
- "utopia-php/system": "0.8.*"
+ "utopia-php/cache": "^0.10.0",
+ "utopia-php/framework": "0.*.*"
},
"require-dev": {
"laravel/pint": "1.2.*",
@@ -2866,76 +2813,32 @@
],
"support": {
"issues": "https://github.com/utopia-php/vcs/issues",
- "source": "https://github.com/utopia-php/vcs/tree/0.9.0"
+ "source": "https://github.com/utopia-php/vcs/tree/0.8.2"
},
- "time": "2024-09-05T16:44:48+00:00"
- },
- {
- "name": "utopia-php/view",
- "version": "0.2.0",
- "source": {
- "type": "git",
- "url": "https://github.com/utopia-php/view.git",
- "reference": "6ee55e83bc014c39ed6b69390f6d399116f65e88"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/utopia-php/view/zipball/6ee55e83bc014c39ed6b69390f6d399116f65e88",
- "reference": "6ee55e83bc014c39ed6b69390f6d399116f65e88",
- "shasum": ""
- },
- "require": {
- "php": ">=8.0"
- },
- "require-dev": {
- "laravel/pint": "^1.2",
- "phpstan/phpstan": "^1.10",
- "phpunit/phpunit": "^9.5.25"
- },
- "type": "library",
- "autoload": {
- "psr-4": {
- "Utopia\\View\\": "src/View"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "description": "A simple, light and advanced PHP rendering engine",
- "keywords": [
- "php",
- "view"
- ],
- "support": {
- "issues": "https://github.com/utopia-php/view/issues",
- "source": "https://github.com/utopia-php/view/tree/0.2.0"
- },
- "time": "2024-04-01T17:21:29+00:00"
+ "time": "2024-08-13T14:36:30+00:00"
},
{
"name": "utopia-php/websocket",
- "version": "0.2.0",
+ "version": "0.1.0",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/websocket.git",
- "reference": "e9d0919b321744a61f12563f5791c47ba9f57810"
+ "reference": "51fcb86171400d8aa40d76c54593481fd273dab5"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/utopia-php/websocket/zipball/e9d0919b321744a61f12563f5791c47ba9f57810",
- "reference": "e9d0919b321744a61f12563f5791c47ba9f57810",
+ "url": "https://api.github.com/repos/utopia-php/websocket/zipball/51fcb86171400d8aa40d76c54593481fd273dab5",
+ "reference": "51fcb86171400d8aa40d76c54593481fd273dab5",
"shasum": ""
},
"require": {
"php": ">=8.0"
},
"require-dev": {
- "laravel/pint": "^1.15",
- "phpstan/phpstan": "^1.8",
"phpunit/phpunit": "^9.5.5",
- "swoole/ide-helper": "5.1.2",
+ "swoole/ide-helper": "4.6.6",
"textalk/websocket": "1.5.2",
+ "vimeo/psalm": "^4.8.1",
"workerman/workerman": "^4.0"
},
"type": "library",
@@ -2948,6 +2851,16 @@
"license": [
"MIT"
],
+ "authors": [
+ {
+ "name": "Eldad Fux",
+ "email": "eldad@appwrite.io"
+ },
+ {
+ "name": "Torsten Dittmann",
+ "email": "torsten@appwrite.io"
+ }
+ ],
"description": "A simple abstraction for WebSocket servers.",
"keywords": [
"framework",
@@ -2958,9 +2871,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/websocket/issues",
- "source": "https://github.com/utopia-php/websocket/tree/0.2.0"
+ "source": "https://github.com/utopia-php/websocket/tree/0.1.0"
},
- "time": "2024-04-09T08:28:11+00:00"
+ "time": "2021-12-20T10:50:09+00:00"
},
{
"name": "webmozart/assert",
@@ -3089,16 +3002,16 @@
"packages-dev": [
{
"name": "appwrite/sdk-generator",
- "version": "0.39.21",
+ "version": "0.39.22",
"source": {
"type": "git",
"url": "https://github.com/appwrite/sdk-generator.git",
- "reference": "9754b190d33aaad56fdb8defc94f90248184c5ac"
+ "reference": "bdbb1607527550e67283ff0533522d1410c2c0df"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/9754b190d33aaad56fdb8defc94f90248184c5ac",
- "reference": "9754b190d33aaad56fdb8defc94f90248184c5ac",
+ "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/bdbb1607527550e67283ff0533522d1410c2c0df",
+ "reference": "bdbb1607527550e67283ff0533522d1410c2c0df",
"shasum": ""
},
"require": {
@@ -3134,9 +3047,9 @@
"description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms",
"support": {
"issues": "https://github.com/appwrite/sdk-generator/issues",
- "source": "https://github.com/appwrite/sdk-generator/tree/0.39.21"
+ "source": "https://github.com/appwrite/sdk-generator/tree/0.39.22"
},
- "time": "2024-09-10T08:49:29+00:00"
+ "time": "2024-10-01T16:16:26+00:00"
},
{
"name": "doctrine/annotations",
@@ -3660,16 +3573,16 @@
},
{
"name": "nikic/php-parser",
- "version": "v5.2.0",
+ "version": "v5.3.0",
"source": {
"type": "git",
"url": "https://github.com/nikic/PHP-Parser.git",
- "reference": "23c79fbbfb725fb92af9bcf41065c8e9a0d49ddb"
+ "reference": "3abf7425cd284141dc5d8d14a9ee444de3345d1a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/23c79fbbfb725fb92af9bcf41065c8e9a0d49ddb",
- "reference": "23c79fbbfb725fb92af9bcf41065c8e9a0d49ddb",
+ "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/3abf7425cd284141dc5d8d14a9ee444de3345d1a",
+ "reference": "3abf7425cd284141dc5d8d14a9ee444de3345d1a",
"shasum": ""
},
"require": {
@@ -3712,9 +3625,9 @@
],
"support": {
"issues": "https://github.com/nikic/PHP-Parser/issues",
- "source": "https://github.com/nikic/PHP-Parser/tree/v5.2.0"
+ "source": "https://github.com/nikic/PHP-Parser/tree/v5.3.0"
},
- "time": "2024-09-15T16:40:33+00:00"
+ "time": "2024-09-29T13:56:26+00:00"
},
{
"name": "phar-io/manifest",
@@ -4326,65 +4239,6 @@
},
"time": "2024-09-26T07:23:32+00:00"
},
- {
- "name": "phpstan/phpstan",
- "version": "1.8.11",
- "source": {
- "type": "git",
- "url": "https://github.com/phpstan/phpstan.git",
- "reference": "46e223dd68a620da18855c23046ddb00940b4014"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/phpstan/phpstan/zipball/46e223dd68a620da18855c23046ddb00940b4014",
- "reference": "46e223dd68a620da18855c23046ddb00940b4014",
- "shasum": ""
- },
- "require": {
- "php": "^7.2|^8.0"
- },
- "conflict": {
- "phpstan/phpstan-shim": "*"
- },
- "bin": [
- "phpstan",
- "phpstan.phar"
- ],
- "type": "library",
- "autoload": {
- "files": [
- "bootstrap.php"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "description": "PHPStan - PHP Static Analysis Tool",
- "keywords": [
- "dev",
- "static analysis"
- ],
- "support": {
- "issues": "https://github.com/phpstan/phpstan/issues",
- "source": "https://github.com/phpstan/phpstan/tree/1.8.11"
- },
- "funding": [
- {
- "url": "https://github.com/ondrejmirtes",
- "type": "github"
- },
- {
- "url": "https://github.com/phpstan",
- "type": "github"
- },
- {
- "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan",
- "type": "tidelift"
- }
- ],
- "time": "2022-10-24T15:45:13+00:00"
- },
{
"name": "phpunit/php-code-coverage",
"version": "9.2.32",
@@ -7150,7 +7004,7 @@
],
"aliases": [],
"minimum-stability": "stable",
- "stability-flags": [],
+ "stability-flags": {},
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
diff --git a/docker-compose.yml b/docker-compose.yml
index 72b488b72c..6ecb0ecff8 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -196,7 +196,7 @@ services:
appwrite-console:
<<: *x-logging
container_name: appwrite-console
- image: appwrite/console:5.0.11
+ image: appwrite/console:5.0.12
restart: unless-stopped
networks:
- appwrite
@@ -682,7 +682,6 @@ services:
entrypoint: maintenance
<<: *x-logging
container_name: appwrite-task-maintenance
- restart: unless-stopped
image: appwrite-dev
networks:
- appwrite
@@ -783,7 +782,6 @@ services:
entrypoint: schedule-functions
<<: *x-logging
container_name: appwrite-task-scheduler-functions
- restart: unless-stopped
image: appwrite-dev
networks:
- appwrite
@@ -812,7 +810,6 @@ services:
entrypoint: schedule-executions
<<: *x-logging
container_name: appwrite-task-scheduler-executions
- restart: unless-stopped
image: appwrite-dev
networks:
- appwrite
@@ -840,7 +837,6 @@ services:
entrypoint: schedule-messages
<<: *x-logging
container_name: appwrite-task-scheduler-messages
- restart: unless-stopped
image: appwrite-dev
networks:
- appwrite
@@ -878,7 +874,7 @@ services:
hostname: exc1
<<: *x-logging
stop_signal: SIGINT
- image: openruntimes/executor:0.6.7
+ image: openruntimes/executor:0.6.11
restart: unless-stopped
networks:
- appwrite
@@ -960,7 +956,7 @@ services:
- MYSQL_USER=${_APP_DB_USER}
- MYSQL_PASSWORD=${_APP_DB_PASS}
- MARIADB_AUTO_UPGRADE=1
- command: 'mysqld --innodb-flush-method=fsync --max_connections=5000'
+ command: "mysqld --innodb-flush-method=fsync"
redis:
image: redis:7.2.4-alpine
diff --git a/docs/tutorials/add-route.md b/docs/tutorials/add-route.md
index 0baa51b5c0..ac6fd40bdb 100644
--- a/docs/tutorials/add-route.md
+++ b/docs/tutorials/add-route.md
@@ -8,7 +8,7 @@ Setting an alias allows the route to be also accessible from the alias URL.
The first parameter specifies the alias URL, the second parameter specifies default values for route parameters.
```php
-Http::post('/v1/storage/buckets/:bucketId/files')
+App::post('/v1/storage/buckets/:bucketId/files')
->alias('/v1/storage/files', ['bucketId' => 'default'])
```
@@ -17,7 +17,7 @@ Http::post('/v1/storage/buckets/:bucketId/files')
Used as an abstract description of the route.
```php
-Http::post('/v1/storage/buckets/:bucketId/files')
+App::post('/v1/storage/buckets/:bucketId/files')
->desc('Create File')
```
@@ -26,14 +26,14 @@ Http::post('/v1/storage/buckets/:bucketId/files')
Groups array is used to group one or more routes with one or more hooks functionality.
```php
-Http::post('/v1/storage/buckets/:bucketId/files')
+App::post('/v1/storage/buckets/:bucketId/files')
->groups(['api'])
```
In the above example groups() is used to define the current route as part of the routes that shares a common init middleware hook.
```php
-Http::init()
+App::init()
->groups(['api'])
->action(
some code.....
@@ -52,7 +52,7 @@ Appwrite uses different labels to achieve different things, for example:
- scope - Defines the route permissions scope.
```php
-Http::post('/v1/storage/buckets/:bucketId/files')
+App::post('/v1/storage/buckets/:bucketId/files')
->label('scope', 'files.write')
```
@@ -66,7 +66,7 @@ Http::post('/v1/storage/buckets/:bucketId/files')
- audits.resource - Signals the extraction part of the resource.
```php
-Http::post('/v1/account/create')
+App::post('/v1/account/create')
->label('audits.event', 'account.create')
->label('audits.resource', 'user/{response.$id}')
->label('audits.userId', '{response.$id}')
@@ -84,7 +84,7 @@ Http::post('/v1/account/create')
* sdk.offline.response.key - JSON property name that has the ID. Defaults to $id
```php
-Http::post('/v1/account/jwt')
+App::post('/v1/account/jwt')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION])
->label('sdk.namespace', 'account')
->label('sdk.method', 'createJWT')
@@ -100,7 +100,7 @@ Http::post('/v1/account/jwt')
- cache.resource - Identifies the cached resource.
```php
-Http::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
+App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
->label('cache', true)
->label('cache.resource', 'file/{request.fileId}')
```
@@ -115,7 +115,7 @@ When using the example below, we configure the abuse mechanism to allow this key
constructed from the combination of the ip, http method, url, userId to hit the route maximum 60 times in 1 hour (60 seconds \* 60 minutes).
```php
-Http::post('/v1/storage/buckets/:bucketId/files')
+App::post('/v1/storage/buckets/:bucketId/files')
->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId}')
->label('abuse-limit', 60)
->label('abuse-time', 3600)
@@ -127,7 +127,7 @@ Http::post('/v1/storage/buckets/:bucketId/files')
Placeholders marked as `[]` are parsed and replaced with their real values.
```php
-Http::post('/v1/storage/buckets/:bucketId/files')
+App::post('/v1/storage/buckets/:bucketId/files')
->label('event', 'buckets.[bucketId].files.[fileId].create')
```
@@ -145,7 +145,7 @@ As the name implies, `param()` is used to define a request parameter.
- An array of injections
```php
-Http::get('/v1/account/logs')
+App::get('/v1/account/logs')
->param('queries', [], new Queries([new Limit(), new Offset()]), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Only supported methods are limit and offset', true)
```
@@ -154,14 +154,14 @@ Http::get('/v1/account/logs')
inject is used to inject dependencies pre-bounded to the app.
```php
-Http::post('/v1/storage/buckets/:bucketId/files')
+App::post('/v1/storage/buckets/:bucketId/files')
->inject('user')
```
-In the example above, the user object is injected into the route pre-bounded using `Http::setResource()`.
+In the example above, the user object is injected into the route pre-bounded using `App::setResource()`.
```php
-Http::setResource('user', function() {
+App::setResource('user', function() {
some code...
});
```
@@ -170,7 +170,7 @@ some code...
Action populates the actual route code and has to be very clear and understandable. A good route stays simple and doesn't contain complex logic. An action is where we describe our business needs in code, and combine different libraries to work together and tell our story.
```php
-Http::post('/v1/account/sessions/anonymous')
+App::post('/v1/account/sessions/anonymous')
->action(function (Request $request) {
some code...
});
diff --git a/phpstan.neon b/phpstan.neon
deleted file mode 100644
index 25771ef17c..0000000000
--- a/phpstan.neon
+++ /dev/null
@@ -1,11 +0,0 @@
-parameters:
- level: 0
- scanDirectories:
- - vendor/swoole/ide-helper
- excludePaths:
- - tests/resources
- ignoreErrors:
- - '#Parameter \$geodb of anonymous function has invalid type MaxMind\\Db\\Reader\.#'
- - '#Parameter \$geodb of function router\(\) has invalid type MaxMind\\Db\\Reader\.#'
- - '#Instantiated class MaxMind\\Db\\Reader not found.#'
- - '#Function scrypt not found\.#'
diff --git a/public/images/integrations/aws-logo.svg b/public/images/integrations/aws-logo.svg
new file mode 100644
index 0000000000..3ab41cde07
--- /dev/null
+++ b/public/images/integrations/aws-logo.svg
@@ -0,0 +1,38 @@
+
+
+
diff --git a/src/Appwrite/Auth/Auth.php b/src/Appwrite/Auth/Auth.php
index 0135020765..1e8109622e 100644
--- a/src/Appwrite/Auth/Auth.php
+++ b/src/Appwrite/Auth/Auth.php
@@ -91,6 +91,37 @@ class Auth
*/
public const MFA_RECENT_DURATION = 1800; // 30 mins
+ /**
+ * @var string
+ */
+ public static $cookieName = 'a_session';
+
+ /**
+ * User Unique ID.
+ *
+ * @var string
+ */
+ public static $unique = '';
+
+ /**
+ * User Secret Key.
+ *
+ * @var string
+ */
+ public static $secret = '';
+
+ /**
+ * Set Cookie Name.
+ *
+ * @param $string
+ *
+ * @return string
+ */
+ public static function setCookieName($string)
+ {
+ return self::$cookieName = $string;
+ }
+
/**
* Encode Session.
*
@@ -408,14 +439,13 @@ class Auth
* Returns all roles for a user.
*
* @param Document $user
- * @param Authorization $auth
* @return array
*/
- public static function getRoles(Document $user, Authorization $auth): array
+ public static function getRoles(Document $user): array
{
$roles = [];
- if (!self::isPrivilegedUser($auth->getRoles()) && !self::isAppUser($auth->getRoles())) {
+ if (!self::isPrivilegedUser(Authorization::getRoles()) && !self::isAppUser(Authorization::getRoles())) {
if ($user->getId()) {
$roles[] = Role::user($user->getId())->toString();
$roles[] = Role::users()->toString();
diff --git a/src/Appwrite/Auth/Authentication.php b/src/Appwrite/Auth/Authentication.php
deleted file mode 100644
index ef372309da..0000000000
--- a/src/Appwrite/Auth/Authentication.php
+++ /dev/null
@@ -1,57 +0,0 @@
-cookieName = $string;
- }
-
- public function getCookieName(): string
- {
- return $this->cookieName;
- }
-
- public function getUnique(): string
- {
- return $this->unique;
- }
-
- public function setUnique(string $unique): void
- {
- $this->unique = $unique;
- }
-
- public function getSecret(): string
- {
- return $this->secret;
- }
-
- public function setSecret(string $secret): void
- {
- $this->secret = $secret;
- }
-
-}
diff --git a/src/Appwrite/Auth/Validator/MockNumber.php b/src/Appwrite/Auth/Validator/MockNumber.php
index 8f0f14c9da..ac5ba89fc5 100644
--- a/src/Appwrite/Auth/Validator/MockNumber.php
+++ b/src/Appwrite/Auth/Validator/MockNumber.php
@@ -2,8 +2,8 @@
namespace Appwrite\Auth\Validator;
-use Utopia\Http\Validator;
-use Utopia\Http\Validator\Text;
+use Utopia\Validator;
+use Utopia\Validator\Text;
/**
* MockNumber.
@@ -52,7 +52,6 @@ class MockNumber extends Validator
return false;
}
- $this->message = '';
return true;
}
diff --git a/src/Appwrite/Auth/Validator/Password.php b/src/Appwrite/Auth/Validator/Password.php
index 913701f7a3..bfe5577889 100644
--- a/src/Appwrite/Auth/Validator/Password.php
+++ b/src/Appwrite/Auth/Validator/Password.php
@@ -2,7 +2,7 @@
namespace Appwrite\Auth\Validator;
-use Utopia\Http\Validator;
+use Utopia\Validator;
/**
* Password.
diff --git a/src/Appwrite/Auth/Validator/Phone.php b/src/Appwrite/Auth/Validator/Phone.php
index d5f6df60c8..26aa687278 100644
--- a/src/Appwrite/Auth/Validator/Phone.php
+++ b/src/Appwrite/Auth/Validator/Phone.php
@@ -2,7 +2,7 @@
namespace Appwrite\Auth\Validator;
-use Utopia\Http\Validator;
+use Utopia\Validator;
/**
* Phone.
diff --git a/src/Appwrite/Event/Event.php b/src/Appwrite/Event/Event.php
index 5e73378743..43eda511df 100644
--- a/src/Appwrite/Event/Event.php
+++ b/src/Appwrite/Event/Event.php
@@ -54,6 +54,7 @@ class Event
protected array $context = [];
protected ?Document $project = null;
protected ?Document $user = null;
+ protected ?string $userId = null;
protected bool $paused = false;
/**
@@ -145,6 +146,18 @@ class Event
return $this;
}
+ /**
+ * Set user ID for this event.
+ *
+ * @return self
+ */
+ public function setUserId(string $userId): self
+ {
+ $this->userId = $userId;
+
+ return $this;
+ }
+
/**
* Get user responsible for triggering this event.
*
@@ -155,6 +168,14 @@ class Event
return $this->user;
}
+ /**
+ * Get user responsible for triggering this event.
+ */
+ public function getUserId(): ?string
+ {
+ return $this->userId;
+ }
+
/**
* Set payload for this event.
*
@@ -303,6 +324,7 @@ class Event
return $client->enqueue([
'project' => $this->project,
'user' => $this->user,
+ 'userId' => $this->userId,
'payload' => $this->payload,
'context' => $this->context,
'events' => Event::generateEvents($this->getEvent(), $this->getParams())
diff --git a/src/Appwrite/Event/Func.php b/src/Appwrite/Event/Func.php
index 1f9f96273c..0cbaf17b60 100644
--- a/src/Appwrite/Event/Func.php
+++ b/src/Appwrite/Event/Func.php
@@ -175,9 +175,9 @@ class Func extends Event
*
* @return string
*/
- public function getBody(): string
+ public function getData(): string
{
- return $this->body;
+ return $this->data;
}
/**
@@ -222,6 +222,7 @@ class Func extends Event
return $client->enqueue([
'project' => $this->project,
'user' => $this->user,
+ 'userId' => $this->userId,
'function' => $this->function,
'functionId' => $this->functionId,
'execution' => $this->execution,
diff --git a/src/Appwrite/Event/Migration.php b/src/Appwrite/Event/Migration.php
index 478291829b..e57ac3c87c 100644
--- a/src/Appwrite/Event/Migration.php
+++ b/src/Appwrite/Event/Migration.php
@@ -81,7 +81,7 @@ class Migration extends Event
return $client->enqueue([
'project' => $this->project,
'user' => $this->user,
- 'migration' => $this->migration
+ 'migration' => $this->migration,
]);
}
}
diff --git a/src/Appwrite/Event/Validator/Event.php b/src/Appwrite/Event/Validator/Event.php
index 2d46bd7727..a3605e4df5 100644
--- a/src/Appwrite/Event/Validator/Event.php
+++ b/src/Appwrite/Event/Validator/Event.php
@@ -3,7 +3,7 @@
namespace Appwrite\Event\Validator;
use Utopia\Config\Config;
-use Utopia\Http\Validator;
+use Utopia\Validator;
class Event extends Validator
{
diff --git a/src/Appwrite/Functions/Validator/Headers.php b/src/Appwrite/Functions/Validator/Headers.php
index 4c30a98045..04003d535b 100644
--- a/src/Appwrite/Functions/Validator/Headers.php
+++ b/src/Appwrite/Functions/Validator/Headers.php
@@ -2,7 +2,7 @@
namespace Appwrite\Functions\Validator;
-use Utopia\Http\Validator;
+use Utopia\Validator;
/**
* Headers.
diff --git a/src/Appwrite/Functions/Validator/Payload.php b/src/Appwrite/Functions/Validator/Payload.php
index 3b2ff4b918..acb461eabd 100644
--- a/src/Appwrite/Functions/Validator/Payload.php
+++ b/src/Appwrite/Functions/Validator/Payload.php
@@ -2,7 +2,7 @@
namespace Appwrite\Functions\Validator;
-use Utopia\Http\Validator\Text;
+use Utopia\Validator\Text;
class Payload extends Text
{
diff --git a/src/Appwrite/Functions/Validator/RuntimeSpecification.php b/src/Appwrite/Functions/Validator/RuntimeSpecification.php
index fa6efe90a4..22311838f6 100644
--- a/src/Appwrite/Functions/Validator/RuntimeSpecification.php
+++ b/src/Appwrite/Functions/Validator/RuntimeSpecification.php
@@ -2,7 +2,7 @@
namespace Appwrite\Functions\Validator;
-use Utopia\Http\Validator;
+use Utopia\Validator;
class RuntimeSpecification extends Validator
{
diff --git a/src/Appwrite/GraphQL/Resolvers.php b/src/Appwrite/GraphQL/Resolvers.php
index 49d7c421f7..8bc72af2f8 100644
--- a/src/Appwrite/GraphQL/Resolvers.php
+++ b/src/Appwrite/GraphQL/Resolvers.php
@@ -6,12 +6,9 @@ use Appwrite\GraphQL\Exception as GQLException;
use Appwrite\Promises\Swoole;
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response;
-use Utopia\DI\Container;
+use Utopia\App;
use Utopia\Exception;
-use Utopia\Http\Http;
-use Utopia\Http\Request as UtopiaHttpRequest;
-use Utopia\Http\Response as UtopiaHttpResponse;
-use Utopia\Http\Route;
+use Utopia\Route;
use Utopia\System\System;
class Resolvers
@@ -19,21 +16,24 @@ class Resolvers
/**
* Create a resolver for a given API {@see Route}.
*
- * @param Http $http
+ * @param App $utopia
* @param ?Route $route
* @return callable
*/
- public function api(
- Http $http,
+ public static function api(
+ App $utopia,
?Route $route,
- UtopiaHttpRequest $request,
- UtopiaHttpResponse $response,
- Container $container,
): callable {
- $resolver = $this;
+ return static fn ($type, $args, $context, $info) => new Swoole(
+ function (callable $resolve, callable $reject) use ($utopia, $route, $args, $context, $info) {
+ /** @var App $utopia */
+ /** @var Response $response */
+ /** @var Request $request */
+
+ $utopia = $utopia->getResource('utopia:graphql', true);
+ $request = $utopia->getResource('request', true);
+ $response = $utopia->getResource('response', true);
- return fn ($type, $args, $context, $info) => new Swoole(
- function (callable $resolve, callable $reject) use ($http, $route, $args, $context, $container, $info, $request, $response, $resolver) {
$path = $route->getPath();
foreach ($args as $key => $value) {
if (\str_contains($path, '/:' . $key)) {
@@ -46,13 +46,14 @@ class Resolvers
switch ($route->getMethod()) {
case 'GET':
- $request->setQuery($args);
+ $request->setQueryString($args);
break;
default:
$request->setPayload($args);
break;
}
- $resolver->resolve($http, $request, $response, $container, $resolve, $reject);
+
+ self::resolve($utopia, $request, $response, $resolve, $reject);
}
);
}
@@ -60,20 +61,20 @@ class Resolvers
/**
* Create a resolver for a document in a specified database and collection with a specific method type.
*
- * @param Http $http
+ * @param App $utopia
* @param string $databaseId
* @param string $collectionId
* @param string $methodType
* @return callable
*/
- public function document(
- Http $http,
+ public static function document(
+ App $utopia,
string $databaseId,
string $collectionId,
string $methodType,
): callable {
return [self::class, 'document' . \ucfirst($methodType)](
- $http,
+ $utopia,
$databaseId,
$collectionId
);
@@ -82,28 +83,28 @@ class Resolvers
/**
* Create a resolver for getting a document in a specified database and collection.
*
- * @param Http $http
+ * @param App $utopia
* @param string $databaseId
* @param string $collectionId
* @param callable $url
* @return callable
*/
- public function documentGet(
- Http $http,
+ public static function documentGet(
+ App $utopia,
string $databaseId,
string $collectionId,
callable $url,
- UtopiaHttpRequest $request,
- UtopiaHttpResponse $response,
- Container $container,
): callable {
- $resolver = $this;
- return fn ($type, $args, $context, $info) => new Swoole(
- function (callable $resolve, callable $reject) use ($http, $databaseId, $collectionId, $url, $type, $args, $container, $request, $response, $resolver) {
+ return static fn ($type, $args, $context, $info) => new Swoole(
+ function (callable $resolve, callable $reject) use ($utopia, $databaseId, $collectionId, $url, $type, $args) {
+ $utopia = $utopia->getResource('utopia:graphql', true);
+ $request = $utopia->getResource('request', true);
+ $response = $utopia->getResource('response', true);
+
$request->setMethod('GET');
$request->setURI($url($databaseId, $collectionId, $args));
- $resolver->resolve($http, $request, $response, $container, $resolve, $reject);
+ self::resolve($utopia, $request, $response, $resolve, $reject);
}
);
}
@@ -111,35 +112,35 @@ class Resolvers
/**
* Create a resolver for listing documents in a specified database and collection.
*
- * @param Http $http
+ * @param App $utopia
* @param string $databaseId
* @param string $collectionId
* @param callable $url
* @param callable $params
* @return callable
*/
- public function documentList(
- Http $http,
+ public static function documentList(
+ App $utopia,
string $databaseId,
string $collectionId,
callable $url,
callable $params,
- UtopiaHttpRequest $request,
- UtopiaHttpResponse $response,
- Container $container,
): callable {
- $resolver = $this;
- return fn ($type, $args, $context, $info) => new Swoole(
- function (callable $resolve, callable $reject) use ($http, $databaseId, $collectionId, $url, $params, $type, $args, $container, $request, $response, $resolver) {
+ return static fn ($type, $args, $context, $info) => new Swoole(
+ function (callable $resolve, callable $reject) use ($utopia, $databaseId, $collectionId, $url, $params, $type, $args) {
+ $utopia = $utopia->getResource('utopia:graphql', true);
+ $request = $utopia->getResource('request', true);
+ $response = $utopia->getResource('response', true);
+
$request->setMethod('GET');
$request->setURI($url($databaseId, $collectionId, $args));
- $request->setQuery($params($databaseId, $collectionId, $args));
+ $request->setQueryString($params($databaseId, $collectionId, $args));
$beforeResolve = function ($payload) {
return $payload['documents'];
};
- $resolver->resolve($http, $request, $response, $container, $resolve, $reject, $beforeResolve);
+ self::resolve($utopia, $request, $response, $resolve, $reject, $beforeResolve);
}
);
}
@@ -147,31 +148,31 @@ class Resolvers
/**
* Create a resolver for creating a document in a specified database and collection.
*
- * @param Http $http
+ * @param App $utopia
* @param string $databaseId
* @param string $collectionId
* @param callable $url
* @param callable $params
* @return callable
*/
- public function documentCreate(
- Http $http,
+ public static function documentCreate(
+ App $utopia,
string $databaseId,
string $collectionId,
callable $url,
callable $params,
- UtopiaHttpRequest $request,
- UtopiaHttpResponse $response,
- Container $container,
): callable {
- $resolver = $this;
- return fn ($type, $args, $context, $info) => new Swoole(
- function (callable $resolve, callable $reject) use ($http, $databaseId, $collectionId, $url, $params, $type, $args, $container, $request, $response, $resolver) {
+ return static fn ($type, $args, $context, $info) => new Swoole(
+ function (callable $resolve, callable $reject) use ($utopia, $databaseId, $collectionId, $url, $params, $type, $args) {
+ $utopia = $utopia->getResource('utopia:graphql', true);
+ $request = $utopia->getResource('request', true);
+ $response = $utopia->getResource('response', true);
+
$request->setMethod('POST');
$request->setURI($url($databaseId, $collectionId, $args));
$request->setPayload($params($databaseId, $collectionId, $args));
- $resolver->resolve($http, $request, $response, $container, $resolve, $reject);
+ self::resolve($utopia, $request, $response, $resolve, $reject);
}
);
}
@@ -179,31 +180,31 @@ class Resolvers
/**
* Create a resolver for updating a document in a specified database and collection.
*
- * @param Http $http
+ * @param App $utopia
* @param string $databaseId
* @param string $collectionId
* @param callable $url
* @param callable $params
* @return callable
*/
- public function documentUpdate(
- Http $http,
+ public static function documentUpdate(
+ App $utopia,
string $databaseId,
string $collectionId,
callable $url,
callable $params,
- UtopiaHttpRequest $request,
- UtopiaHttpResponse $response,
- Container $container,
): callable {
- $resolver = $this;
- return fn ($type, $args, $context, $info) => new Swoole(
- function (callable $resolve, callable $reject) use ($http, $databaseId, $collectionId, $url, $params, $type, $args, $container, $request, $response, $resolver) {
+ return static fn ($type, $args, $context, $info) => new Swoole(
+ function (callable $resolve, callable $reject) use ($utopia, $databaseId, $collectionId, $url, $params, $type, $args) {
+ $utopia = $utopia->getResource('utopia:graphql', true);
+ $request = $utopia->getResource('request', true);
+ $response = $utopia->getResource('response', true);
+
$request->setMethod('PATCH');
$request->setURI($url($databaseId, $collectionId, $args));
$request->setPayload($params($databaseId, $collectionId, $args));
- $resolver->resolve($http, $request, $response, $container, $resolve, $reject);
+ self::resolve($utopia, $request, $response, $resolve, $reject);
}
);
}
@@ -211,34 +212,34 @@ class Resolvers
/**
* Create a resolver for deleting a document in a specified database and collection.
*
- * @param Http $http
+ * @param App $utopia
* @param string $databaseId
* @param string $collectionId
* @param callable $url
* @return callable
*/
- public function documentDelete(
- Http $http,
+ public static function documentDelete(
+ App $utopia,
string $databaseId,
string $collectionId,
callable $url,
- UtopiaHttpRequest $request,
- UtopiaHttpResponse $response,
- Container $container,
): callable {
- $resolver = $this;
- return fn ($type, $args, $context, $info) => new Swoole(
- function (callable $resolve, callable $reject) use ($http, $databaseId, $collectionId, $url, $type, $args, $container, $request, $response, $resolver) {
+ return static fn ($type, $args, $context, $info) => new Swoole(
+ function (callable $resolve, callable $reject) use ($utopia, $databaseId, $collectionId, $url, $type, $args) {
+ $utopia = $utopia->getResource('utopia:graphql', true);
+ $request = $utopia->getResource('request', true);
+ $response = $utopia->getResource('response', true);
+
$request->setMethod('DELETE');
$request->setURI($url($databaseId, $collectionId, $args));
- $resolver->resolve($http, $request, $response, $container, $resolve, $reject);
+ self::resolve($utopia, $request, $response, $resolve, $reject);
}
);
}
/**
- * @param Http $http
+ * @param App $utopia
* @param Request $request
* @param Response $response
* @param callable $resolve
@@ -248,11 +249,10 @@ class Resolvers
* @return void
* @throws Exception
*/
- private function resolve(
- Http $http,
+ private static function resolve(
+ App $utopia,
Request $request,
Response $response,
- Container $context,
callable $resolve,
callable $reject,
?callable $beforeResolve = null,
@@ -263,16 +263,14 @@ class Resolvers
$request->removeHeader('content-type');
}
+ $request = clone $request;
+ $utopia->setResource('request', static fn () => $request);
$response->setContentType(Response::CONTENT_TYPE_NULL);
try {
- $context
- ->refresh('cache')
- ->refresh('dbForProject')
- ->refresh('dbForConsole')
- ->refresh('getProjectDb');
+ $route = $utopia->match($request, fresh: true);
- $http->run(clone $context);
+ $utopia->execute($route, $request, $response);
} catch (\Throwable $e) {
if ($beforeReject) {
$e = $beforeReject($e);
@@ -287,12 +285,10 @@ class Resolvers
if ($beforeReject) {
$payload = $beforeReject($payload);
}
- $reject(
- new GQLException(
- message: $payload['message'],
- code: $response->getStatusCode()
- )
- );
+ $reject(new GQLException(
+ message: $payload['message'],
+ code: $response->getStatusCode()
+ ));
return;
}
diff --git a/src/Appwrite/GraphQL/Schema.php b/src/Appwrite/GraphQL/Schema.php
index 2b05f08aee..833ea9d032 100644
--- a/src/Appwrite/GraphQL/Schema.php
+++ b/src/Appwrite/GraphQL/Schema.php
@@ -3,63 +3,54 @@
namespace Appwrite\GraphQL;
use Appwrite\GraphQL\Types\Mapper;
-use Appwrite\Utopia\Response\Models;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema as GQLSchema;
-use Utopia\DI\Container;
+use Utopia\App;
use Utopia\Exception;
-use Utopia\Http\Adapter\Swoole\Response as UtopiaSwooleResponse;
-use Utopia\Http\Http;
-use Utopia\Http\Request;
-use Utopia\Http\Response as UtopiaHttpResponse;
-use Utopia\Http\Route;
+use Utopia\Route;
class Schema
{
- protected ?GQLSchema $schema = null;
- protected array $dirty = [];
+ protected static ?GQLSchema $schema = null;
+ protected static array $dirty = [];
/**
*
- * @param Http $http
- * @param callable $complexity Function to calculate complexity
- * @param callable $attributes Function to get attributes
- * @param array $urls Array of functions to get urls for specific method types
- * @param array $params Array of functions to build parameters for specific method types
+ * @param App $utopia
+ * @param callable $complexity Function to calculate complexity
+ * @param callable $attributes Function to get attributes
+ * @param array $urls Array of functions to get urls for specific method types
+ * @param array $params Array of functions to build parameters for specific method types
* @return GQLSchema
* @throws Exception
*/
- public function build(
- Http $http,
- Request $request,
- UtopiaHttpResponse $response,
- Container $container,
+ public static function build(
+ App $utopia,
callable $complexity,
callable $attributes,
array $urls,
array $params,
): GQLSchema {
- if (!empty($this->schema)) {
- return $this->schema;
+ App::setResource('utopia:graphql', static function () use ($utopia) {
+ return $utopia;
+ });
+
+ if (!empty(self::$schema)) {
+ return self::$schema;
}
- $api = $this->api(
- $http,
- $request,
- $response,
- $container,
+ $api = static::api(
+ $utopia,
$complexity
);
- // $collections = $this->collections(
- // $http,
- // $complexity,
- // $request,
- // $response,
- // $attributes,
- // $urls,
- // $params,
- // );
+ //$collections = static::collections(
+ // $utopia,
+ // $complexity,
+ // $attributes,
+ // $urls,
+ // $params,
+ //);
$queries = \array_merge_recursive(
$api['query'],
@@ -73,7 +64,7 @@ class Schema
\ksort($queries);
\ksort($mutations);
- return $this->schema = new GQLSchema([
+ return static::$schema = new GQLSchema([
'query' => new ObjectType([
'name' => 'Query',
'fields' => $queries
@@ -89,23 +80,21 @@ class Schema
* This function iterates all API routes and builds a GraphQL
* schema defining types and resolvers for all response models.
*
- * @param Http $http
- * @param Request $request
- * @param UtopiaSwooleResponse $response
+ * @param App $utopia
* @param callable $complexity
* @return array
- * @throws \Exception
+ * @throws Exception
*/
- protected function api(Http $http, Request $request, UtopiaHttpResponse $response, Container $container, callable $complexity): array
+ protected static function api(App $utopia, callable $complexity): array
{
- Mapper::init(Models::getModels());
-
- $mapper = new Mapper();
+ Mapper::init($utopia
+ ->getResource('response')
+ ->getModels());
$queries = [];
$mutations = [];
- foreach ($http->getRoutes() as $routes) {
+ foreach ($utopia->getRoutes() as $routes) {
foreach ($routes as $route) {
/** @var Route $route */
@@ -117,7 +106,7 @@ class Schema
continue;
}
- foreach ($mapper->route($http, $route, $request, $response, $container, $complexity) as $field) {
+ foreach (Mapper::route($utopia, $route, $complexity) as $field) {
switch ($route->getMethod()) {
case 'GET':
$queries[$name] = $field;
@@ -145,7 +134,7 @@ class Schema
* Iterates all of a projects attributes and builds GraphQL
* queries and mutations for the collections they make up.
*
- * @param Http $http
+ * @param App $utopia
* @param callable $complexity
* @param callable $attributes
* @param array $urls
@@ -154,7 +143,7 @@ class Schema
* @throws \Exception
*/
protected static function collections(
- Http $http,
+ App $utopia,
callable $complexity,
callable $attributes,
array $urls,
@@ -205,36 +194,36 @@ class Schema
$queryFields[$collectionId . 'Get'] = [
'type' => $objectType,
'args' => Mapper::args('id'),
- /*'resolve' => Resolvers::documentGet(
- $http,
+ 'resolve' => Resolvers::documentGet(
+ $utopia,
$databaseId,
$collectionId,
$urls['get'],
- )*/
+ )
];
$queryFields[$collectionId . 'List'] = [
'type' => Type::listOf($objectType),
'args' => Mapper::args('list'),
- /*'resolve' => Resolvers::documentList(
- $http,
+ 'resolve' => Resolvers::documentList(
+ $utopia,
$databaseId,
$collectionId,
$urls['list'],
$params['list'],
- ),*/
+ ),
'complexity' => $complexity,
];
$mutationFields[$collectionId . 'Create'] = [
'type' => $objectType,
'args' => $attributes,
- /*'resolve' => Resolvers::documentCreate(
- $http,
+ 'resolve' => Resolvers::documentCreate(
+ $utopia,
$databaseId,
$collectionId,
$urls['create'],
$params['create'],
- )*/
+ )
];
$mutationFields[$collectionId . 'Update'] = [
'type' => $objectType,
@@ -245,23 +234,23 @@ class Schema
$attributes
)
),
- /*'resolve' => Resolvers::documentUpdate(
- $http,
+ 'resolve' => Resolvers::documentUpdate(
+ $utopia,
$databaseId,
$collectionId,
$urls['update'],
$params['update'],
- )*/
+ )
];
$mutationFields[$collectionId . 'Delete'] = [
'type' => Mapper::model('none'),
'args' => Mapper::args('id'),
- /*'resolve' => Resolvers::documentDelete(
- $http,
+ 'resolve' => Resolvers::documentDelete(
+ $utopia,
$databaseId,
$collectionId,
$urls['delete'],
- )*/
+ )
];
}
$offset += $limit;
@@ -273,8 +262,8 @@ class Schema
];
}
- public function setDirty(string $projectId): void
+ public static function setDirty(string $projectId): void
{
- $this->dirty[$projectId] = true;
+ self::$dirty[$projectId] = true;
}
}
diff --git a/src/Appwrite/GraphQL/Types/Mapper.php b/src/Appwrite/GraphQL/Types/Mapper.php
index a15c6aa475..d8f1d7da09 100644
--- a/src/Appwrite/GraphQL/Types/Mapper.php
+++ b/src/Appwrite/GraphQL/Types/Mapper.php
@@ -8,13 +8,10 @@ use Exception;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\UnionType;
-use Utopia\DI\Container;
-use Utopia\Http\Adapter\Swoole\Response as UtopiaSwooleResponse;
-use Utopia\Http\Http;
-use Utopia\Http\Request;
-use Utopia\Http\Route;
-use Utopia\Http\Validator;
-use Utopia\Http\Validator\Nullable;
+use Utopia\App;
+use Utopia\Route;
+use Utopia\Validator;
+use Utopia\Validator\Nullable;
class Mapper
{
@@ -53,6 +50,7 @@ class Mapper
$defaults = [
'boolean' => Type::boolean(),
'string' => Type::string(),
+ 'payload' => Type::string(),
'integer' => Type::int(),
'double' => Type::float(),
'datetime' => Type::string(),
@@ -77,15 +75,12 @@ class Mapper
return self::$args[$key] ?? [];
}
- public function route(
- Http $http,
+ public static function route(
+ App $utopia,
Route $route,
- Request $request,
- UtopiaSwooleResponse $response,
- Container $container,
callable $complexity
): iterable {
- foreach (static::$blacklist as $blacklist) {
+ foreach (self::$blacklist as $blacklist) {
if (\str_starts_with($route->getPath(), $blacklist)) {
return;
}
@@ -107,7 +102,7 @@ class Mapper
$list = true;
}
$parameterType = Mapper::param(
- $container,
+ $utopia,
$parameter['validator'],
!$parameter['optional'],
$parameter['injections']
@@ -122,7 +117,7 @@ class Mapper
'type' => $type,
'description' => $description,
'args' => $params,
- 'resolve' => (new Resolvers())->api($http, $route, $request, $response, $container)
+ 'resolve' => Resolvers::api($utopia, $route)
];
if ($list) {
@@ -211,7 +206,7 @@ class Mapper
/**
* Map a {@see Route} parameter to a GraphQL Type
*
- * @param Container $container
+ * @param App $utopia
* @param Validator|callable $validator
* @param bool $required
* @param array $injections
@@ -219,13 +214,13 @@ class Mapper
* @throws Exception
*/
public static function param(
- Container $container,
+ App $utopia,
Validator|callable $validator,
bool $required,
array $injections
): Type {
$validator = \is_callable($validator)
- ? \call_user_func_array($validator, array_map(fn ($injection) => $container->get($injection), $injections))
+ ? \call_user_func_array($validator, $utopia->getResources($injections))
: $validator;
$isNullable = $validator instanceof Nullable;
@@ -238,20 +233,20 @@ class Mapper
case 'Appwrite\Network\Validator\CNAME':
case 'Appwrite\Task\Validator\Cron':
case 'Appwrite\Utopia\Database\Validator\CustomId':
- case 'Utopia\Http\Validator\Domain':
+ case 'Utopia\Validator\Domain':
case 'Appwrite\Network\Validator\Email':
case 'Appwrite\Event\Validator\Event':
case 'Appwrite\Event\Validator\FunctionEvent':
- case 'Utopia\Http\Validator\HexColor':
- case 'Utopia\Http\Validator\Host':
- case 'Utopia\Http\Validator\IP':
+ case 'Utopia\Validator\HexColor':
+ case 'Utopia\Validator\Host':
+ case 'Utopia\Validator\IP':
case 'Utopia\Database\Validator\Key':
- case 'Utopia\Http\Validator\Origin':
+ case 'Utopia\Validator\Origin':
case 'Appwrite\Auth\Validator\Password':
- case 'Utopia\Http\Validator\Text':
+ case 'Utopia\Validator\Text':
case 'Utopia\Database\Validator\UID':
- case 'Utopia\Http\Validator\URL':
- case 'Utopia\Http\Validator\WhiteList':
+ case 'Utopia\Validator\URL':
+ case 'Utopia\Validator\WhiteList':
default:
$type = Type::string();
break;
@@ -279,29 +274,29 @@ class Mapper
case 'Appwrite\Utopia\Database\Validator\Queries\Variables':
$type = Type::listOf(Type::string());
break;
- case 'Utopia\Http\Validator\Boolean':
+ case 'Utopia\Validator\Boolean':
$type = Type::boolean();
break;
- case 'Utopia\Http\Validator\ArrayList':
+ case 'Utopia\Validator\ArrayList':
$type = Type::listOf(self::param(
- $container,
+ $utopia,
$validator->getValidator(),
$required,
$injections
));
break;
- case 'Utopia\Http\Validator\Integer':
- case 'Utopia\Http\Validator\Numeric':
- case 'Utopia\Http\Validator\Range':
+ case 'Utopia\Validator\Integer':
+ case 'Utopia\Validator\Numeric':
+ case 'Utopia\Validator\Range':
$type = Type::int();
break;
- case 'Utopia\Http\Validator\FloatValidator':
+ case 'Utopia\Validator\FloatValidator':
$type = Type::float();
break;
- case 'Utopia\Http\Validator\Assoc':
+ case 'Utopia\Validator\Assoc':
$type = Types::assoc();
break;
- case 'Utopia\Http\Validator\JSON':
+ case 'Utopia\Validator\JSON':
$type = Types::json();
break;
case 'Utopia\Storage\Validator\File':
diff --git a/src/Appwrite/Messaging/Adapter/Realtime.php b/src/Appwrite/Messaging/Adapter/Realtime.php
index 55d8db2924..d0d4a7c725 100644
--- a/src/Appwrite/Messaging/Adapter/Realtime.php
+++ b/src/Appwrite/Messaging/Adapter/Realtime.php
@@ -243,7 +243,11 @@ class Realtime extends Adapter
* @param string $event
* @param Document $payload
* @param Document|null $project
+ * @param Document|null $database
+ * @param Document|null $collection
+ * @param Document|null $bucket
* @return array
+ * @throws \Exception
*/
public static function fromPayload(string $event, Document $payload, Document $project = null, Document $database = null, Document $collection = null, Document $bucket = null): array
{
@@ -262,6 +266,7 @@ class Realtime extends Adapter
break;
case 'rules':
$channels[] = 'console';
+ $channels[] = 'projects.' . $project->getId();
$projectId = 'console';
$roles = [Role::team($project->getAttribute('teamId'))->toString()];
break;
@@ -280,6 +285,7 @@ class Realtime extends Adapter
case 'databases':
if (in_array($parts[4] ?? [], ['attributes', 'indexes'])) {
$channels[] = 'console';
+ $channels[] = 'projects.' . $project->getId();
$projectId = 'console';
$roles = [Role::team($project->getAttribute('teamId'))->toString()];
} elseif (($parts[4] ?? '') === 'documents') {
@@ -319,6 +325,7 @@ class Realtime extends Adapter
if ($parts[2] === 'executions') {
if (!empty($payload->getRead())) {
$channels[] = 'console';
+ $channels[] = 'projects.' . $project->getId();
$channels[] = 'executions';
$channels[] = 'executions.' . $payload->getId();
$channels[] = 'functions.' . $payload->getAttribute('functionId');
@@ -326,6 +333,7 @@ class Realtime extends Adapter
}
} elseif ($parts[2] === 'deployments') {
$channels[] = 'console';
+ $channels[] = 'projects.' . $project->getId();
$projectId = 'console';
$roles = [Role::team($project->getAttribute('teamId'))->toString()];
}
@@ -333,6 +341,7 @@ class Realtime extends Adapter
break;
case 'migrations':
$channels[] = 'console';
+ $channels[] = 'projects.' . $project->getId();
$projectId = 'console';
$roles = [Role::team($project->getAttribute('teamId'))->toString()];
break;
diff --git a/src/Appwrite/Migration/Migration.php b/src/Appwrite/Migration/Migration.php
index 840903affe..cee1b2d263 100644
--- a/src/Appwrite/Migration/Migration.php
+++ b/src/Appwrite/Migration/Migration.php
@@ -88,7 +88,8 @@ abstract class Migration
'1.5.7' => 'V20',
'1.5.8' => 'V20',
'1.5.9' => 'V20',
- '1.5.10' => 'V20',
+ '1.5.10' => 'V20',
+ '1.5.11' => 'V20',
'1.6.0' => 'V21',
];
@@ -97,10 +98,10 @@ abstract class Migration
*/
protected array $collections;
- public function __construct(Authorization $auth)
+ public function __construct()
{
- $auth->disable();
- $auth->setDefaultStatus(false);
+ Authorization::disable();
+ Authorization::setDefaultStatus(false);
$this->collections = Config::getParam('collections', []);
@@ -175,23 +176,25 @@ abstract class Migration
Console::log('Migrating Collection ' . $collection['$id'] . ':');
foreach ($this->documentsIterator($collection['$id']) as $document) {
- if (empty($document->getId()) || empty($document->getCollection())) {
- return;
- }
+ go(function (Document $document, callable $callback) {
+ if (empty($document->getId()) || empty($document->getCollection())) {
+ return;
+ }
- $old = $document->getArrayCopy();
- $new = call_user_func($callback, $document);
+ $old = $document->getArrayCopy();
+ $new = call_user_func($callback, $document);
- if (is_null($new) || $new->getArrayCopy() == $old) {
- return;
- }
+ if (is_null($new) || $new->getArrayCopy() == $old) {
+ return;
+ }
- try {
- $this->projectDB->updateDocument($document->getCollection(), $document->getId(), $document);
- } catch (\Throwable $th) {
- Console::error('Failed to update document: ' . $th->getMessage());
- return;
- }
+ try {
+ $this->projectDB->updateDocument($document->getCollection(), $document->getId(), $document);
+ } catch (\Throwable $th) {
+ Console::error('Failed to update document: ' . $th->getMessage());
+ return;
+ }
+ }, $document, $callback);
}
}
}
diff --git a/src/Appwrite/Network/Validator/CNAME.php b/src/Appwrite/Network/Validator/CNAME.php
index e9e2b586a5..e1ae061c84 100644
--- a/src/Appwrite/Network/Validator/CNAME.php
+++ b/src/Appwrite/Network/Validator/CNAME.php
@@ -2,7 +2,7 @@
namespace Appwrite\Network\Validator;
-use Utopia\Http\Validator;
+use Utopia\Validator;
class CNAME extends Validator
{
diff --git a/src/Appwrite/Network/Validator/Email.php b/src/Appwrite/Network/Validator/Email.php
index bae0ff0bbf..3209a4aada 100644
--- a/src/Appwrite/Network/Validator/Email.php
+++ b/src/Appwrite/Network/Validator/Email.php
@@ -2,14 +2,14 @@
namespace Appwrite\Network\Validator;
-use Utopia\Http\Validator;
+use Utopia\Validator;
/**
* Email
*
* Validate that an variable is a valid email address
*
- * @package Utopia\Http\Validator
+ * @package Utopia\Validator
*/
class Email extends Validator
{
diff --git a/src/Appwrite/Network/Validator/Origin.php b/src/Appwrite/Network/Validator/Origin.php
index 573a59b844..d41e9af2ad 100644
--- a/src/Appwrite/Network/Validator/Origin.php
+++ b/src/Appwrite/Network/Validator/Origin.php
@@ -2,8 +2,8 @@
namespace Appwrite\Network\Validator;
-use Utopia\Http\Validator;
-use Utopia\Http\Validator\Hostname;
+use Utopia\Validator;
+use Utopia\Validator\Hostname;
class Origin extends Validator
{
diff --git a/src/Appwrite/Platform/Tasks/Doctor.php b/src/Appwrite/Platform/Tasks/Doctor.php
index 3bf9e0d33b..82d1ca2d59 100644
--- a/src/Appwrite/Platform/Tasks/Doctor.php
+++ b/src/Appwrite/Platform/Tasks/Doctor.php
@@ -3,17 +3,13 @@
namespace Appwrite\Platform\Tasks;
use Appwrite\ClamAV\Network;
-use Appwrite\Utopia\Queue\Connections;
+use Utopia\App;
use Utopia\CLI\Console;
use Utopia\Config\Config;
-use Utopia\Database\Adapter\MariaDB;
-use Utopia\Database\Adapter\MySQL;
use Utopia\Domains\Domain;
use Utopia\DSN\DSN;
-use Utopia\Http\Http;
use Utopia\Logger\Logger;
use Utopia\Platform\Action;
-use Utopia\Queue\Connection\Redis;
use Utopia\Registry\Registry;
use Utopia\Storage\Device\Local;
use Utopia\Storage\Storage;
@@ -31,11 +27,10 @@ class Doctor extends Action
$this
->desc('Validate server health')
->inject('register')
- ->inject('connections')
- ->callback(fn (Registry $register, Connections $connections) => $this->action($register, $connections));
+ ->callback(fn (Registry $register) => $this->action($register));
}
- public function action(Registry $register, Connections $connections): void
+ public function action(Registry $register): void
{
Console::log(" __ ____ ____ _ _ ____ __ ____ ____ __ __
/ _\ ( _ \( _ \/ )( \( _ \( )(_ _)( __) ( )/ \
@@ -130,73 +125,17 @@ class Doctor extends Action
//throw $th;
}
- /** @var array $pools */
- $pools = $register->get('pools');
+ $pools = $register->get('pools'); /** @var \Utopia\Pools\Group $pools */
$configs = [
- 'Console.DB' => [
- 'prefix' => 'console',
- 'databases' => Config::getParam('pools-console')
- ],
- 'Database.DB' => [
- 'prefix' => 'database',
- 'databases' => Config::getParam('pools-database')
- ],
+ 'Console.DB' => Config::getParam('pools-console'),
+ 'Projects.DB' => Config::getParam('pools-database'),
];
-
foreach ($configs as $key => $config) {
- foreach ($config['databases'] as $database) {
+ foreach ($config as $database) {
try {
- $pool = $pools['pools-' . $config['prefix'] . '-' . $database]['pool'];
- $dsn = $pools['pools-' . $config['prefix'] . '-' . $database]['dsn'];
-
- $connection = $pool->get();
- $connections->add($connection, $pool);
- $adapter = match ($dsn->getScheme()) {
- 'mariadb' => new MariaDB($connection),
- 'mysql' => new MySQL($connection),
- default => null
- };
- $adapter->setDatabase($dsn->getPath());
-
-
- if ($adapter->ping()) {
- Console::success('🟢 ' . str_pad("$key({$database})", 50, '.') . 'connected');
- } else {
- Console::error('🔴 ' . str_pad("$key({$database})", 47, '.') . 'disconnected');
- }
- } catch (\Throwable $th) {
- Console::error('🔴 ' . str_pad("{$key}.({$database})", 47, '.') . 'disconnected');
- }
- }
- }
-
- /** @var array $pools */
- $pools = $register->get('pools');
- $configs = [
- 'Cache' => [
- 'prefix' => 'cache',
- 'databases' => Config::getParam('pools-cache')
- ],
- 'Queue' => [
- 'prefix' => 'queue',
- 'databases' => Config::getParam('pools-queue')
- ],
- 'PubSub' => [
- 'prefix' => 'pubsub',
- 'databases' => Config::getParam('pools-pubsub')
- ],
- ];
- foreach ($configs as $key => $config) {
- foreach ($config['databases'] as $database) {
- try {
- $pool = $pools['pools-' . $config['prefix'] . '-' . $database]['pool'];
- $dsn = $pools['pools-' . $config['prefix'] . '-' . $database]['dsn'];
- $connection = $pool->get();
- $connections->add($connection, $pool);
-
- $adapter = new Redis($dsn->getHost(), $dsn->getPort());
+ $adapter = $pools->get($database)->pop()->getResource();
if ($adapter->ping()) {
Console::success('🟢 ' . str_pad("{$key}({$database})", 50, '.') . 'connected');
@@ -204,7 +143,30 @@ class Doctor extends Action
Console::error('🔴 ' . str_pad("{$key}({$database})", 47, '.') . 'disconnected');
}
} catch (\Throwable $th) {
- Console::error('🔴 ' . str_pad("{$key}({$database})", 47, '.') . 'disconnected');
+ Console::error('🔴 ' . str_pad("{$key}.({$database})", 47, '.') . 'disconnected');
+ }
+ }
+ }
+
+ $pools = $register->get('pools'); /** @var \Utopia\Pools\Group $pools */
+ $configs = [
+ 'Cache' => Config::getParam('pools-cache'),
+ 'Queue' => Config::getParam('pools-queue'),
+ 'PubSub' => Config::getParam('pools-pubsub'),
+ ];
+
+ foreach ($configs as $key => $config) {
+ foreach ($config as $pool) {
+ try {
+ $adapter = $pools->get($pool)->pop()->getResource();
+
+ if ($adapter->ping()) {
+ Console::success('🟢 ' . str_pad("{$key}({$pool})", 50, '.') . 'connected');
+ } else {
+ Console::error('🔴 ' . str_pad("{$key}({$pool})", 47, '.') . 'disconnected');
+ }
+ } catch (\Throwable $th) {
+ Console::error('🔴 ' . str_pad("{$key}({$pool})", 47, '.') . 'disconnected');
}
}
}
@@ -296,7 +258,7 @@ class Doctor extends Action
}
try {
- if (Http::isProduction()) {
+ if (App::isProduction()) {
Console::log('');
$version = \json_decode(@\file_get_contents(System::getEnv('_APP_HOME', 'http://localhost') . '/version'), true);
diff --git a/src/Appwrite/Platform/Tasks/Install.php b/src/Appwrite/Platform/Tasks/Install.php
index a1a73e6385..4abd267684 100644
--- a/src/Appwrite/Platform/Tasks/Install.php
+++ b/src/Appwrite/Platform/Tasks/Install.php
@@ -8,9 +8,9 @@ use Appwrite\Docker\Env;
use Appwrite\Utopia\View;
use Utopia\CLI\Console;
use Utopia\Config\Config;
-use Utopia\Http\Validator\Boolean;
-use Utopia\Http\Validator\Text;
use Utopia\Platform\Action;
+use Utopia\Validator\Boolean;
+use Utopia\Validator\Text;
class Install extends Action
{
@@ -213,7 +213,8 @@ class Install extends Action
}
$env = '';
- $output = '';
+ $stdout = '';
+ $stderr = '';
foreach ($input as $key => $value) {
if ($value) {
@@ -224,13 +225,13 @@ class Install extends Action
$exit = 0;
if (!$noStart) {
Console::log("Running \"docker compose up -d --remove-orphans --renew-anon-volumes\"");
- $exit = Console::execute("$env docker compose --project-directory $this->path up -d --remove-orphans --renew-anon-volumes", '', $output);
+ $exit = Console::execute("$env docker compose --project-directory $this->path up -d --remove-orphans --renew-anon-volumes", '', $stdout, $stderr);
}
if ($exit !== 0) {
$message = 'Failed to install Appwrite dockers';
Console::error($message);
- Console::error($output);
+ Console::error($stderr);
Console::exit($exit);
} else {
$message = 'Appwrite installed successfully';
diff --git a/src/Appwrite/Platform/Tasks/Maintenance.php b/src/Appwrite/Platform/Tasks/Maintenance.php
index 1da8a58ebe..afb38f35fc 100644
--- a/src/Appwrite/Platform/Tasks/Maintenance.php
+++ b/src/Appwrite/Platform/Tasks/Maintenance.php
@@ -49,12 +49,7 @@ class Maintenance extends Action
$this->foreachProject($dbForConsole, function (Document $project) use ($queueForDeletes, $usageStatsRetentionHourly) {
$queueForDeletes->setProject($project);
- $this->notifyDeleteTargets($queueForDeletes);
- $this->notifyDeleteExecutionLogs($queueForDeletes);
- $this->notifyDeleteAbuseLogs($queueForDeletes);
- $this->notifyDeleteAuditLogs($queueForDeletes);
- $this->notifyDeleteUsageStats($usageStatsRetentionHourly, $queueForDeletes);
- $this->notifyDeleteExpiredSessions($queueForDeletes);
+ $this->notifyProjects($queueForDeletes, $usageStatsRetentionHourly);
});
$this->notifyDeleteConnections($queueForDeletes);
@@ -64,6 +59,19 @@ class Maintenance extends Action
}, $interval, $delay);
}
+ /**
+ * Hook to allow sub-classes to extend project-level maintenance functionality.
+ */
+ protected function notifyProjects(Delete $queueForDeletes, int $usageStatsRetentionHourly): void
+ {
+ $this->notifyDeleteTargets($queueForDeletes);
+ $this->notifyDeleteExecutionLogs($queueForDeletes);
+ $this->notifyDeleteAbuseLogs($queueForDeletes);
+ $this->notifyDeleteAuditLogs($queueForDeletes);
+ $this->notifyDeleteUsageStats($usageStatsRetentionHourly, $queueForDeletes);
+ $this->notifyDeleteExpiredSessions($queueForDeletes);
+ }
+
protected function foreachProject(Database $dbForConsole, callable $callback): void
{
// TODO: @Meldiron name of this method no longer matches. It does not delete, and it gives whole document
diff --git a/src/Appwrite/Platform/Tasks/Migrate.php b/src/Appwrite/Platform/Tasks/Migrate.php
index 55be0b81c2..dcba59bb1d 100644
--- a/src/Appwrite/Platform/Tasks/Migrate.php
+++ b/src/Appwrite/Platform/Tasks/Migrate.php
@@ -10,10 +10,10 @@ use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
-use Utopia\Http\Validator\Text;
use Utopia\Platform\Action;
use Utopia\Registry\Registry;
use Utopia\System\System;
+use Utopia\Validator\Text;
class Migrate extends Action
{
@@ -30,15 +30,12 @@ class Migrate extends Action
->desc('Migrate Appwrite to new version')
/** @TODO APP_VERSION_STABLE needs to be defined */
->param('version', APP_VERSION_STABLE, new Text(8), 'Version to migrate to.', true)
- ->inject('cache')
->inject('dbForConsole')
->inject('getProjectDB')
->inject('register')
- ->inject('authorization')
- ->inject('console')
- ->callback(function ($version, $dbForConsole, $getProjectDB, Registry $register, Authorization $authorization, Document $console) {
- \Co\run(function () use ($version, $dbForConsole, $getProjectDB, $register, $authorization, $console) {
- $this->action($version, $dbForConsole, $getProjectDB, $register, $authorization, $console);
+ ->callback(function ($version, $dbForConsole, $getProjectDB, Registry $register) {
+ \Co\run(function () use ($version, $dbForConsole, $getProjectDB, $register) {
+ $this->action($version, $dbForConsole, $getProjectDB, $register);
});
});
}
@@ -61,12 +58,13 @@ class Migrate extends Action
}
}
- public function action(string $version, Database $dbForConsole, callable $getProjectDB, Registry $register, Authorization $auth, Document $console)
+ public function action(string $version, Database $dbForConsole, callable $getProjectDB, Registry $register)
{
- $auth->disable();
+ Authorization::disable();
if (!array_key_exists($version, Migration::$versions)) {
Console::error("Version {$version} not found.");
Console::exit(1);
+
return;
}
@@ -79,8 +77,12 @@ class Migrate extends Action
10
);
+ $app = new App('UTC');
+
Console::success('Starting Data Migration to version ' . $version);
+ $console = $app->getResource('console');
+
$limit = 30;
$sum = 30;
$offset = 0;
@@ -99,7 +101,7 @@ class Migrate extends Action
$class = 'Appwrite\\Migration\\Version\\' . Migration::$versions[$version];
/** @var Migration $migration */
- $migration = new $class($auth, );
+ $migration = new $class();
while (!empty($projects)) {
foreach ($projects as $project) {
diff --git a/src/Appwrite/Platform/Tasks/QueueCount.php b/src/Appwrite/Platform/Tasks/QueueCount.php
index 63f829073a..b02165c1d2 100644
--- a/src/Appwrite/Platform/Tasks/QueueCount.php
+++ b/src/Appwrite/Platform/Tasks/QueueCount.php
@@ -3,11 +3,11 @@
namespace Appwrite\Platform\Tasks;
use Utopia\CLI\Console;
-use Utopia\Http\Validator\Text;
-use Utopia\Http\Validator\WhiteList;
use Utopia\Platform\Action;
use Utopia\Queue\Client;
use Utopia\Queue\Connection;
+use Utopia\Validator\Text;
+use Utopia\Validator\WhiteList;
class QueueCount extends Action
{
diff --git a/src/Appwrite/Platform/Tasks/QueueRetry.php b/src/Appwrite/Platform/Tasks/QueueRetry.php
index 63f6c8e11e..b6139dc177 100644
--- a/src/Appwrite/Platform/Tasks/QueueRetry.php
+++ b/src/Appwrite/Platform/Tasks/QueueRetry.php
@@ -3,11 +3,11 @@
namespace Appwrite\Platform\Tasks;
use Utopia\CLI\Console;
-use Utopia\Http\Validator\Text;
-use Utopia\Http\Validator\Wildcard;
use Utopia\Platform\Action;
use Utopia\Queue\Client;
use Utopia\Queue\Connection;
+use Utopia\Validator\Text;
+use Utopia\Validator\Wildcard;
class QueueRetry extends Action
{
diff --git a/src/Appwrite/Platform/Tasks/SSL.php b/src/Appwrite/Platform/Tasks/SSL.php
index ad4098d9ee..5af0cb6cd8 100644
--- a/src/Appwrite/Platform/Tasks/SSL.php
+++ b/src/Appwrite/Platform/Tasks/SSL.php
@@ -5,10 +5,10 @@ namespace Appwrite\Platform\Tasks;
use Appwrite\Event\Certificate;
use Utopia\CLI\Console;
use Utopia\Database\Document;
-use Utopia\Http\Validator\Boolean;
-use Utopia\Http\Validator\Hostname;
use Utopia\Platform\Action;
use Utopia\System\System;
+use Utopia\Validator\Boolean;
+use Utopia\Validator\Hostname;
class SSL extends Action
{
diff --git a/src/Appwrite/Platform/Tasks/ScheduleBase.php b/src/Appwrite/Platform/Tasks/ScheduleBase.php
index cb02ec60fd..a1b85c341f 100644
--- a/src/Appwrite/Platform/Tasks/ScheduleBase.php
+++ b/src/Appwrite/Platform/Tasks/ScheduleBase.php
@@ -2,44 +2,41 @@
namespace Appwrite\Platform\Tasks;
-use Appwrite\Utopia\Queue\Connections;
use Swoole\Timer;
use Utopia\CLI\Console;
+use Utopia\Database\Database;
use Utopia\Database\DateTime;
use Utopia\Database\Document;
use Utopia\Database\Exception;
use Utopia\Database\Query;
use Utopia\Platform\Action;
+use Utopia\Pools\Group;
use Utopia\System\System;
+use function Swoole\Coroutine\run;
+
abstract class ScheduleBase extends Action
{
protected const UPDATE_TIMER = 10; //seconds
protected const ENQUEUE_TIMER = 60; //seconds
protected array $schedules = [];
- protected Connections $connections;
abstract public static function getName(): string;
-
abstract public static function getSupportedResource(): string;
-
- abstract protected function enqueueResources(
- array $pools,
- callable $getConsoleDB
- );
+ abstract public static function getCollectionId(): string;
+ abstract protected function enqueueResources(Group $pools, Database $dbForConsole, callable $getProjectDB): void;
public function __construct()
{
- $this->connections = new Connections();
$type = static::getSupportedResource();
$this
->desc("Execute {$type}s scheduled in Appwrite")
->inject('pools')
- ->inject('getConsoleDB')
+ ->inject('dbForConsole')
->inject('getProjectDB')
- ->callback(fn (array $pools, callable $getConsoleDB, callable $getProjectDB) => $this->action($pools, $getConsoleDB, $getProjectDB));
+ ->callback(fn (Group $pools, Database $dbForConsole, callable $getProjectDB) => $this->action($pools, $dbForConsole, $getProjectDB));
}
/**
@@ -47,12 +44,11 @@ abstract class ScheduleBase extends Action
* 2. Create timer that sync all changes from 'schedules' collection to local copy. Only reading changes thanks to 'resourceUpdatedAt' attribute
* 3. Create timer that prepares coroutines for soon-to-execute schedules. When it's ready, coroutine sleeps until exact time before sending request to worker.
*/
- public function action(array $pools, callable $getConsoleDB, callable $getProjectDB): void
+ public function action(Group $pools, Database $dbForConsole, callable $getProjectDB): void
{
Console::title(\ucfirst(static::getSupportedResource()) . ' scheduler V1');
Console::success(APP_NAME . ' ' . \ucfirst(static::getSupportedResource()) . ' scheduler v1 has started');
- [$_, $_, $dbForConsole] = $getConsoleDB();
/**
* Extract only necessary attributes to lower memory used.
*
@@ -63,14 +59,8 @@ abstract class ScheduleBase extends Action
$getSchedule = function (Document $schedule) use ($dbForConsole, $getProjectDB): array {
$project = $dbForConsole->getDocument('projects', $schedule->getAttribute('projectId'));
- $collectionId = match ($schedule->getAttribute('resourceType')) {
- 'function' => 'functions',
- 'message' => 'messages',
- 'execution' => 'executions'
- };
-
$resource = $getProjectDB($project)->getDocument(
- $collectionId,
+ static::getCollectionId(),
$schedule->getAttribute('resourceId')
);
@@ -114,12 +104,7 @@ abstract class ScheduleBase extends Action
try {
$this->schedules[$document->getInternalId()] = $getSchedule($document);
} catch (\Throwable $th) {
- $collectionId = match ($document->getAttribute('resourceType')) {
- 'function' => 'functions',
- 'message' => 'messages',
- 'execution' => 'executions'
- };
-
+ $collectionId = static::getCollectionId();
Console::error("Failed to load schedule for project {$document['projectId']} {$collectionId} {$document['resourceId']}");
Console::error($th->getMessage());
}
@@ -128,74 +113,76 @@ abstract class ScheduleBase extends Action
$latestDocument = \end($results);
}
+ $pools->reclaim();
Console::success("{$total} resources were loaded in " . (\microtime(true) - $loadStart) . " seconds");
Console::success("Starting timers at " . DateTime::now());
+ run(function () use ($dbForConsole, &$lastSyncUpdate, $getSchedule, $pools, $getProjectDB) {
+ /**
+ * The timer synchronize $schedules copy with database collection.
+ */
+ Timer::tick(static::UPDATE_TIMER * 1000, function () use ($dbForConsole, &$lastSyncUpdate, $getSchedule, $pools) {
+ $time = DateTime::now();
+ $timerStart = \microtime(true);
- Timer::tick(static::UPDATE_TIMER * 1000, function () use ($getConsoleDB, &$lastSyncUpdate, $getSchedule, $pools) {
- [$connection,$pool, $dbForConsole] = $getConsoleDB();
- $connections = new Connections();
- $connections->add($connection, $pool);
+ $limit = 1000;
+ $sum = $limit;
+ $total = 0;
+ $latestDocument = null;
- $time = DateTime::now();
- $timerStart = \microtime(true);
+ Console::log("Sync tick: Running at $time");
- $limit = 1000;
- $sum = $limit;
- $total = 0;
- $latestDocument = null;
+ while ($sum === $limit) {
+ $paginationQueries = [Query::limit($limit)];
- Console::log("Sync tick: Running at $time");
-
- while ($sum === $limit) {
- $paginationQueries = [Query::limit($limit)];
-
- if ($latestDocument) {
- $paginationQueries[] = Query::cursorAfter($latestDocument);
- }
-
- $results = $dbForConsole->find('schedules', \array_merge($paginationQueries, [
- Query::equal('region', [System::getEnv('_APP_REGION', 'default')]),
- Query::equal('resourceType', [static::getSupportedResource()]),
- Query::greaterThanEqual('resourceUpdatedAt', $lastSyncUpdate),
- ]));
-
- $sum = count($results);
- $total = $total + $sum;
-
- foreach ($results as $document) {
- $localDocument = $schedules[$document['resourceId']] ?? null;
-
- // Check if resource has been updated since last sync
- $org = $localDocument !== null ? \strtotime($localDocument['resourceUpdatedAt']) : null;
- $new = \strtotime($document['resourceUpdatedAt']);
-
- if (!$document['active']) {
- Console::info("Removing: {$document['resourceId']}");
- unset($this->schedules[$document->getInternalId()]);
- } elseif ($new !== $org) {
- Console::info("Updating: {$document['resourceId']}");
- $this->schedules[$document->getInternalId()] = $getSchedule($document);
+ if ($latestDocument) {
+ $paginationQueries[] = Query::cursorAfter($latestDocument);
}
+
+ $results = $dbForConsole->find('schedules', \array_merge($paginationQueries, [
+ Query::equal('region', [System::getEnv('_APP_REGION', 'default')]),
+ Query::equal('resourceType', [static::getSupportedResource()]),
+ Query::greaterThanEqual('resourceUpdatedAt', $lastSyncUpdate),
+ ]));
+
+ $sum = count($results);
+ $total = $total + $sum;
+
+ foreach ($results as $document) {
+ $localDocument = $this->schedules[$document->getInternalId()] ?? null;
+
+ // Check if resource has been updated since last sync
+ $org = $localDocument !== null ? \strtotime($localDocument['resourceUpdatedAt']) : null;
+ $new = \strtotime($document['resourceUpdatedAt']);
+
+ if (!$document['active']) {
+ Console::info("Removing: {$document['resourceType']}::{$document['resourceId']}");
+ unset($this->schedules[$document->getInternalId()]);
+ } elseif ($new !== $org) {
+ Console::info("Updating: {$document['resourceType']}::{$document['resourceId']}");
+ $this->schedules[$document->getInternalId()] = $getSchedule($document);
+ }
+ }
+
+ $latestDocument = \end($results);
}
- $latestDocument = \end($results);
- }
+ $lastSyncUpdate = $time;
+ $timerEnd = \microtime(true);
- $lastSyncUpdate = $time;
- $timerEnd = \microtime(true);
+ $pools->reclaim();
- $connections->reclaim();
- Console::log("Sync tick: {$total} schedules were updated in " . ($timerEnd - $timerStart) . " seconds");
+ Console::log("Sync tick: {$total} schedules were updated in " . ($timerEnd - $timerStart) . " seconds");
+ });
+
+ Timer::tick(
+ static::ENQUEUE_TIMER * 1000,
+ fn () => $this->enqueueResources($pools, $dbForConsole, $getProjectDB)
+ );
+
+ $this->enqueueResources($pools, $dbForConsole, $getProjectDB);
});
-
- Timer::tick(
- static::ENQUEUE_TIMER * 1000,
- fn () => $this->enqueueResources($pools, $getConsoleDB)
- );
-
- $this->enqueueResources($pools, $getConsoleDB);
}
}
diff --git a/src/Appwrite/Platform/Tasks/ScheduleExecutions.php b/src/Appwrite/Platform/Tasks/ScheduleExecutions.php
index 245f810c85..73a2814397 100644
--- a/src/Appwrite/Platform/Tasks/ScheduleExecutions.php
+++ b/src/Appwrite/Platform/Tasks/ScheduleExecutions.php
@@ -4,7 +4,8 @@ namespace Appwrite\Platform\Tasks;
use Appwrite\Event\Func;
use Swoole\Coroutine as Co;
-use Utopia\Queue\Connection\Redis;
+use Utopia\Database\Database;
+use Utopia\Pools\Group;
class ScheduleExecutions extends ScheduleBase
{
@@ -21,16 +22,16 @@ class ScheduleExecutions extends ScheduleBase
return 'execution';
}
- protected function enqueueResources(array $pools, callable $getConsoleDB): void
+ public static function getCollectionId(): string
{
- [$connection,$pool, $dbForConsole] = $getConsoleDB();
- $this->connections->add($connection, $pool);
+ return 'executions';
+ }
- $pool = $pools['pools-queue-queue']['pool'];
- $connection = $pool->get();
- $this->connections->add($connection, $pool);
-
- $queueForFunctions = new Func(new Redis($connection));
+ protected function enqueueResources(Group $pools, Database $dbForConsole, callable $getProjectDB): void
+ {
+ $queue = $pools->get('queue')->pop();
+ $connection = $queue->getResource();
+ $queueForFunctions = new Func($connection);
$intervalEnd = (new \DateTime())->modify('+' . self::ENQUEUE_TIMER . ' seconds');
foreach ($this->schedules as $schedule) {
@@ -49,34 +50,38 @@ class ScheduleExecutions extends ScheduleBase
continue;
}
+ $data = $dbForConsole->getDocument(
+ 'schedules',
+ $schedule['$id'],
+ )->getAttribute('data', []);
+
$delay = $scheduledAt->getTimestamp() - (new \DateTime())->getTimestamp();
-
- \go(function () use ($queueForFunctions, $schedule, $delay, $dbForConsole) {
+ \go(function () use ($queueForFunctions, $schedule, $delay, $data) {
Co::sleep($delay);
- $queueForFunctions
- ->setType('schedule')
+ $queueForFunctions->setType('schedule')
// Set functionId instead of function as we don't have $dbForProject
// TODO: Refactor to use function instead of functionId
->setFunctionId($schedule['resource']['functionId'])
->setExecution($schedule['resource'])
- ->setMethod($schedule['data']['method'] ?? 'POST')
- ->setPath($schedule['data']['path'] ?? '/')
- ->setHeaders($schedule['data']['headers'] ?? [])
- ->setBody($schedule['data']['body'] ?? '')
+ ->setMethod($data['method'] ?? 'POST')
+ ->setPath($data['path'] ?? '/')
+ ->setHeaders($data['headers'] ?? [])
+ ->setBody($data['body'] ?? '')
->setProject($schedule['project'])
+ ->setUserId($data['userId'] ?? '')
->trigger();
-
- $dbForConsole->deleteDocument(
- 'schedules',
- $schedule['$id'],
- );
});
+ $dbForConsole->deleteDocument(
+ 'schedules',
+ $schedule['$id'],
+ );
+
unset($this->schedules[$schedule['$internalId']]);
}
- $this->connections->reclaim();
+ $queue->reclaim();
}
}
diff --git a/src/Appwrite/Platform/Tasks/ScheduleFunctions.php b/src/Appwrite/Platform/Tasks/ScheduleFunctions.php
index 450551400e..4d57902330 100644
--- a/src/Appwrite/Platform/Tasks/ScheduleFunctions.php
+++ b/src/Appwrite/Platform/Tasks/ScheduleFunctions.php
@@ -5,8 +5,9 @@ namespace Appwrite\Platform\Tasks;
use Appwrite\Event\Func;
use Cron\CronExpression;
use Utopia\CLI\Console;
+use Utopia\Database\Database;
use Utopia\Database\DateTime;
-use Utopia\Queue\Connection\Redis;
+use Utopia\Pools\Group;
class ScheduleFunctions extends ScheduleBase
{
@@ -25,7 +26,12 @@ class ScheduleFunctions extends ScheduleBase
return 'function';
}
- protected function enqueueResources(array $pools, callable $getConsoleDB): void
+ public static function getCollectionId(): string
+ {
+ return 'functions';
+ }
+
+ protected function enqueueResources(Group $pools, Database $dbForConsole, callable $getProjectDB): void
{
$timerStart = \microtime(true);
$time = DateTime::now();
@@ -67,11 +73,8 @@ class ScheduleFunctions extends ScheduleBase
\go(function () use ($delay, $scheduleKeys, $pools) {
\sleep($delay); // in seconds
- $pool = $pools['pools-queue-queue']['pool'];
- $connection = $pool->get();
- $this->connections->add($connection, $pool);
-
- $queueConnection = new Redis($connection);
+ $queue = $pools->get('queue')->pop();
+ $connection = $queue->getResource();
foreach ($scheduleKeys as $scheduleKey) {
// Ensure schedule was not deleted
@@ -81,7 +84,7 @@ class ScheduleFunctions extends ScheduleBase
$schedule = $this->schedules[$scheduleKey];
- $queueForFunctions = new Func($queueConnection);
+ $queueForFunctions = new Func($connection);
$queueForFunctions
->setType('schedule')
@@ -92,8 +95,7 @@ class ScheduleFunctions extends ScheduleBase
->trigger();
}
- $this->connections->reclaim();
- // $queue->reclaim(); // TODO: Do in try/catch/finally, or add to connectons resource
+ $queue->reclaim();
});
}
diff --git a/src/Appwrite/Platform/Tasks/ScheduleMessages.php b/src/Appwrite/Platform/Tasks/ScheduleMessages.php
index 72e1a5f786..b9d8e2a282 100644
--- a/src/Appwrite/Platform/Tasks/ScheduleMessages.php
+++ b/src/Appwrite/Platform/Tasks/ScheduleMessages.php
@@ -3,7 +3,8 @@
namespace Appwrite\Platform\Tasks;
use Appwrite\Event\Messaging;
-use Utopia\Queue\Connection\Redis;
+use Utopia\Database\Database;
+use Utopia\Pools\Group;
class ScheduleMessages extends ScheduleBase
{
@@ -20,11 +21,13 @@ class ScheduleMessages extends ScheduleBase
return 'message';
}
- protected function enqueueResources(array $pools, callable $getConsoleDB): void
+ public static function getCollectionId(): string
{
- [$connection,$pool, $dbForConsole] = $getConsoleDB();
- $this->connections->add($connection, $pool);
+ return 'messages';
+ }
+ protected function enqueueResources(Group $pools, Database $dbForConsole, callable $getProjectDB): void
+ {
foreach ($this->schedules as $schedule) {
if (!$schedule['active']) {
continue;
@@ -37,15 +40,10 @@ class ScheduleMessages extends ScheduleBase
continue;
}
- \go(function () use ($now, $schedule, $pools, $dbForConsole) {
- $pool = $pools['pools-queue-queue']['pool'];
- $dsn = $pools['pools-queue-queue']['dsn'];
- $connection = $pool->get();
- $this->connections->add($connection, $pool);
-
- $queueConnection = new Redis($dsn->getHost(), $dsn->getPort());
-
- $queueForMessaging = new Messaging($queueConnection);
+ \go(function () use ($schedule, $pools, $dbForConsole) {
+ $queue = $pools->get('queue')->pop();
+ $connection = $queue->getResource();
+ $queueForMessaging = new Messaging($connection);
$queueForMessaging
->setType(MESSAGE_SEND_TYPE_EXTERNAL)
@@ -58,7 +56,8 @@ class ScheduleMessages extends ScheduleBase
$schedule['$id'],
);
- $this->connections->reclaim();
+ $queue->reclaim();
+
unset($this->schedules[$schedule['$internalId']]);
});
}
diff --git a/src/Appwrite/Platform/Tasks/Specs.php b/src/Appwrite/Platform/Tasks/Specs.php
index 6e69798d98..f71de98d95 100644
--- a/src/Appwrite/Platform/Tasks/Specs.php
+++ b/src/Appwrite/Platform/Tasks/Specs.php
@@ -5,27 +5,25 @@ namespace Appwrite\Platform\Tasks;
use Appwrite\Specification\Format\OpenAPI3;
use Appwrite\Specification\Format\Swagger2;
use Appwrite\Specification\Specification;
-use Appwrite\Utopia\Response;
-use Appwrite\Utopia\Response\Models;
+use Appwrite\Utopia\Request as AppwriteRequest;
+use Appwrite\Utopia\Response as AppwriteResponse;
use Exception;
-use Swoole\Http\Request as SwooleHttpRequest;
-use Swoole\Http\Response as SwooleHttpResponse;
+use Swoole\Http\Request as SwooleRequest;
+use Swoole\Http\Response as SwooleResponse;
+use Utopia\App;
use Utopia\Cache\Adapter\None;
use Utopia\Cache\Cache;
use Utopia\CLI\Console;
use Utopia\Config\Config;
use Utopia\Database\Adapter\MySQL;
use Utopia\Database\Database;
-use Utopia\DI\Container;
-use Utopia\DI\Dependency;
-use Utopia\Http\Adapter\FPM\Server;
-use Utopia\Http\Adapter\Swoole\Request;
-use Utopia\Http\Adapter\Swoole\Response as HttpResponse;
-use Utopia\Http\Http;
-use Utopia\Http\Validator\Text;
-use Utopia\Http\Validator\WhiteList;
use Utopia\Platform\Action;
+use Utopia\Registry\Registry;
+use Utopia\Request as UtopiaRequest;
+use Utopia\Response as UtopiaResponse;
use Utopia\System\System;
+use Utopia\Validator\Text;
+use Utopia\Validator\WhiteList;
class Specs extends Action
{
@@ -34,38 +32,37 @@ class Specs extends Action
return 'specs';
}
+ public function getRequest(): UtopiaRequest
+ {
+ return new AppwriteRequest(new SwooleRequest());
+ }
+
+ public function getResponse(): UtopiaResponse
+ {
+ return new AppwriteResponse(new SwooleResponse());
+ }
+
public function __construct()
{
$this
->desc('Generate Appwrite API specifications')
->param('version', 'latest', new Text(16), 'Spec version', true)
->param('mode', 'normal', new WhiteList(['normal', 'mocks']), 'Spec Mode', true)
- ->inject('context')
- ->callback(fn (string $version, string $mode, Container $context) => $this->action($version, $mode, $context));
+ ->inject('register')
+ ->callback(fn (string $version, string $mode, Registry $register) => $this->action($version, $mode, $register));
}
- public function action(string $version, string $mode, Container $container): void
+ public function action(string $version, string $mode, Registry $register): void
{
- $appRoutes = Http::getRoutes();
- $response = new Response(new HttpResponse(new SwooleHttpResponse()));
+ $appRoutes = App::getRoutes();
+ $response = $this->getResponse();
$mocks = ($mode === 'mocks');
- $requestDependency = new Dependency();
- $responseDependency = new Dependency();
- $dbForConsole = new Dependency();
- $dbForProject = new Dependency();
-
// Mock dependencies
- $requestDependency->setName('request')->setCallback(fn () => new Request(new SwooleHttpRequest()));
- $responseDependency->setName('response')->setCallback(fn () => $response);
- $dbForConsole->setName('dbForConsole')->setCallback(fn () => new Database(new MySQL(''), new Cache(new None())));
- $dbForProject->setName('dbForProject')->setCallback(fn () => new Database(new MySQL(''), new Cache(new None())));
-
- $container
- ->set($requestDependency)
- ->set($responseDependency)
- ->set($dbForProject)
- ->set($dbForConsole);
+ App::setResource('request', fn () => $this->getRequest());
+ App::setResource('response', fn () => $response);
+ App::setResource('dbForConsole', fn () => new Database(new MySQL(''), new Cache(new None())));
+ App::setResource('dbForProject', fn () => new Database(new MySQL(''), new Cache(new None())));
$platforms = [
'client' => APP_PLATFORM_CLIENT,
@@ -199,10 +196,8 @@ class Specs extends Action
case APP_AUTH_TYPE_SESSION:
$sdkPlatforms[] = APP_PLATFORM_CLIENT;
break;
- case APP_AUTH_TYPE_KEY:
- $sdkPlatforms[] = APP_PLATFORM_SERVER;
- break;
case APP_AUTH_TYPE_JWT:
+ case APP_AUTH_TYPE_KEY:
$sdkPlatforms[] = APP_PLATFORM_SERVER;
break;
case APP_AUTH_TYPE_ADMIN:
@@ -257,7 +252,7 @@ class Specs extends Action
];
}
- $models = Models::getModels();
+ $models = $response->getModels();
foreach ($models as $key => $value) {
if ($platform !== APP_PLATFORM_CONSOLE && !$value->isPublic()) {
@@ -265,7 +260,7 @@ class Specs extends Action
}
}
- $arguments = [new Http(new Server(), $container, 'UTC'), $services, $routes, $models, $keys[$platform], $authCounts[$platform] ?? 0];
+ $arguments = [new App('UTC'), $services, $routes, $models, $keys[$platform], $authCounts[$platform] ?? 0];
foreach (['swagger2', 'open-api3'] as $format) {
$formatInstance = match ($format) {
'swagger2' => new Swagger2(...$arguments),
diff --git a/src/Appwrite/Platform/Tasks/Upgrade.php b/src/Appwrite/Platform/Tasks/Upgrade.php
index 608b924c06..341ce42fc4 100644
--- a/src/Appwrite/Platform/Tasks/Upgrade.php
+++ b/src/Appwrite/Platform/Tasks/Upgrade.php
@@ -3,8 +3,8 @@
namespace Appwrite\Platform\Tasks;
use Utopia\CLI\Console;
-use Utopia\Http\Validator\Boolean;
-use Utopia\Http\Validator\Text;
+use Utopia\Validator\Boolean;
+use Utopia\Validator\Text;
class Upgrade extends Install
{
diff --git a/src/Appwrite/Platform/Workers/Audits.php b/src/Appwrite/Platform/Workers/Audits.php
index d21f1c267d..86ca59d3fd 100644
--- a/src/Appwrite/Platform/Workers/Audits.php
+++ b/src/Appwrite/Platform/Workers/Audits.php
@@ -9,7 +9,6 @@ use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Exception\Authorization;
use Utopia\Database\Exception\Structure;
-use Utopia\Database\Validator\Authorization as ValidatorAuthorization;
use Utopia\Platform\Action;
use Utopia\Queue\Message;
@@ -29,8 +28,7 @@ class Audits extends Action
->desc('Audits worker')
->inject('message')
->inject('dbForProject')
- ->inject('authorization')
- ->callback(fn ($message, $dbForProject, ValidatorAuthorization $authorization) => $this->action($message, $dbForProject, $authorization));
+ ->callback(fn ($message, $dbForProject) => $this->action($message, $dbForProject));
}
@@ -43,7 +41,7 @@ class Audits extends Action
* @throws Authorization
* @throws Structure
*/
- public function action(Message $message, Database $dbForProject, ValidatorAuthorization $auth): void
+ public function action(Message $message, Database $dbForProject): void
{
$payload = $message->getPayload() ?? [];
diff --git a/src/Appwrite/Platform/Workers/Builds.php b/src/Appwrite/Platform/Workers/Builds.php
index 9274e7430c..5dd2f7f886 100644
--- a/src/Appwrite/Platform/Workers/Builds.php
+++ b/src/Appwrite/Platform/Workers/Builds.php
@@ -54,8 +54,7 @@ class Builds extends Action
->inject('dbForProject')
->inject('deviceForFunctions')
->inject('log')
- ->inject('authorization')
- ->callback(fn ($message, Database $dbForConsole, Event $queueForEvents, Func $queueForFunctions, Usage $usage, Cache $cache, Database $dbForProject, Device $deviceForFunctions, Log $log, Authorization $authorization) => $this->action($message, $dbForConsole, $queueForEvents, $queueForFunctions, $usage, $cache, $dbForProject, $deviceForFunctions, $log, $authorization));
+ ->callback(fn ($message, Database $dbForConsole, Event $queueForEvents, Func $queueForFunctions, Usage $usage, Cache $cache, Database $dbForProject, Device $deviceForFunctions, Log $log) => $this->action($message, $dbForConsole, $queueForEvents, $queueForFunctions, $usage, $cache, $dbForProject, $deviceForFunctions, $log));
}
/**
@@ -68,11 +67,10 @@ class Builds extends Action
* @param Database $dbForProject
* @param Device $deviceForFunctions
* @param Log $log
- * @param Authorization $auth
* @return void
* @throws \Utopia\Database\Exception
*/
- public function action(Message $message, Database $dbForConsole, Event $queueForEvents, Func $queueForFunctions, Usage $queueForUsage, Cache $cache, Database $dbForProject, Device $deviceForFunctions, Log $log, Authorization $auth): void
+ public function action(Message $message, Database $dbForConsole, Event $queueForEvents, Func $queueForFunctions, Usage $queueForUsage, Cache $cache, Database $dbForProject, Device $deviceForFunctions, Log $log): void
{
$payload = $message->getPayload() ?? [];
@@ -94,7 +92,7 @@ class Builds extends Action
case BUILD_TYPE_RETRY:
Console::info('Creating build for deployment: ' . $deployment->getId());
$github = new GitHub($cache);
- $this->buildDeployment($deviceForFunctions, $queueForFunctions, $queueForEvents, $queueForUsage, $dbForConsole, $dbForProject, $github, $project, $resource, $deployment, $template, $log, $auth);
+ $this->buildDeployment($deviceForFunctions, $queueForFunctions, $queueForEvents, $queueForUsage, $dbForConsole, $dbForProject, $github, $project, $resource, $deployment, $template, $log);
break;
default:
@@ -115,12 +113,11 @@ class Builds extends Action
* @param Document $deployment
* @param Document $template
* @param Log $log
- * @param Authorization $auth
* @return void
* @throws \Utopia\Database\Exception
* @throws Exception
*/
- protected function buildDeployment(Device $deviceForFunctions, Func $queueForFunctions, Event $queueForEvents, Usage $queueForUsage, Database $dbForConsole, Database $dbForProject, GitHub $github, Document $project, Document $function, Document $deployment, Document $template, Log $log, Authorization $auth): void
+ protected function buildDeployment(Device $deviceForFunctions, Func $queueForFunctions, Event $queueForEvents, Usage $queueForUsage, Database $dbForConsole, Database $dbForProject, GitHub $github, Document $project, Document $function, Document $deployment, Document $template, Log $log): void
{
$executor = new Executor(System::getEnv('_APP_EXECUTOR_HOST'));
@@ -224,18 +221,20 @@ class Builds extends Action
$templateRootDirectory = \ltrim($templateRootDirectory, '/');
if (!empty($templateRepositoryName) && !empty($templateOwnerName) && !empty($templateVersion)) {
- $output = '';
+ $stdout = '';
+ $stderr = '';
+
// Clone template repo
$tmpTemplateDirectory = '/tmp/builds/' . $buildId . '-template';
$gitCloneCommandForTemplate = $github->generateCloneCommand($templateOwnerName, $templateRepositoryName, $templateVersion, GitHub::CLONE_TYPE_TAG, $tmpTemplateDirectory, $templateRootDirectory);
- $exit = Console::execute($gitCloneCommandForTemplate, '', $output);
+ $exit = Console::execute($gitCloneCommandForTemplate, '', $stdout, $stderr);
if ($exit !== 0) {
- throw new \Exception('Unable to clone code repository: ' . $output);
+ throw new \Exception('Unable to clone code repository: ' . $stderr);
}
// Ensure directories
- Console::execute('mkdir -p ' . \escapeshellarg($tmpTemplateDirectory . '/' . $templateRootDirectory), '', $output);
+ Console::execute('mkdir -p ' . \escapeshellarg($tmpTemplateDirectory . '/' . $templateRootDirectory), '', $stdout, $stderr);
$tmpPathFile = $tmpTemplateDirectory . '/code.tar.gz';
@@ -246,7 +245,7 @@ class Builds extends Action
}
$tarParamDirectory = \escapeshellarg($tmpTemplateDirectory . (empty($templateRootDirectory) ? '' : '/' . $templateRootDirectory));
- Console::execute('tar --exclude code.tar.gz -czf ' . \escapeshellarg($tmpPathFile) . ' -C ' . \escapeshellcmd($tarParamDirectory) . ' .', '', $output); // TODO: Replace escapeshellcmd with escapeshellarg if we find a way that doesnt break syntax
+ Console::execute('tar --exclude code.tar.gz -czf ' . \escapeshellarg($tmpPathFile) . ' -C ' . \escapeshellcmd($tarParamDirectory) . ' .', '', $stdout, $stderr); // TODO: Replace escapeshellcmd with escapeshellarg if we find a way that doesnt break syntax
$source = $deviceForFunctions->getPath($deployment->getId() . '.' . \pathinfo('code.tar.gz', PATHINFO_EXTENSION));
$result = $localDevice->transfer($tmpPathFile, $source, $deviceForFunctions);
@@ -255,7 +254,7 @@ class Builds extends Action
throw new \Exception("Unable to move file");
}
- Console::execute('rm -rf ' . \escapeshellarg($tmpTemplateDirectory), '', $output);
+ Console::execute('rm -rf ' . \escapeshellarg($tmpTemplateDirectory), '', $stdout, $stderr);
$directorySize = $deviceForFunctions->getFileSize($source);
$build = $dbForProject->updateDocument('builds', $build->getId(), $build->setAttribute('source', $source));
@@ -277,7 +276,6 @@ class Builds extends Action
$branchName = $deployment->getAttribute('providerBranch');
$commitHash = $deployment->getAttribute('providerCommitHash', '');
- $output = '';
$cloneVersion = $branchName;
$cloneType = GitHub::CLONE_TYPE_BRANCH;
@@ -285,18 +283,22 @@ class Builds extends Action
$cloneVersion = $commitHash;
$cloneType = GitHub::CLONE_TYPE_COMMIT;
}
+
$gitCloneCommand = $github->generateCloneCommand($cloneOwner, $cloneRepository, $cloneVersion, $cloneType, $tmpDirectory, $rootDirectory);
- Console::execute('mkdir -p ' . \escapeshellarg('/tmp/builds/' . $buildId), '', $output);
+ $stdout = '';
+ $stderr = '';
+
+ Console::execute('mkdir -p ' . \escapeshellarg('/tmp/builds/' . $buildId), '', $stdout, $stderr);
if ($dbForProject->getDocument('builds', $buildId)->getAttribute('status') === 'canceled') {
Console::info('Build has been canceled');
return;
}
- $exit = Console::execute($gitCloneCommand, '', $output);
+ $exit = Console::execute($gitCloneCommand, '', $stdout, $stderr);
if ($exit !== 0) {
- throw new \Exception('Unable to clone code repository: ' . $output);
+ throw new \Exception('Unable to clone code repository: ' . $stderr);
}
// Local refactoring for function folder with spaces
@@ -304,10 +306,10 @@ class Builds extends Action
$rootDirectoryWithoutSpaces = str_replace(' ', '', $rootDirectory);
$from = $tmpDirectory . '/' . $rootDirectory;
$to = $tmpDirectory . '/' . $rootDirectoryWithoutSpaces;
- $exit = Console::execute('mv "' . \escapeshellarg($from) . '" "' . \escapeshellarg($to) . '"', '', $output);
+ $exit = Console::execute('mv "' . \escapeshellarg($from) . '" "' . \escapeshellarg($to) . '"', '', $stdout, $stderr);
if ($exit !== 0) {
- throw new \Exception('Unable to move function with spaces' . $output);
+ throw new \Exception('Unable to move function with spaces' . $stderr);
}
$rootDirectory = $rootDirectoryWithoutSpaces;
}
@@ -328,33 +330,33 @@ class Builds extends Action
$tmpTemplateDirectory = '/tmp/builds/' . $buildId . '/template';
$gitCloneCommandForTemplate = $github->generateCloneCommand($templateOwnerName, $templateRepositoryName, $templateVersion, GitHub::CLONE_TYPE_TAG, $tmpTemplateDirectory, $templateRootDirectory);
- $exit = Console::execute($gitCloneCommandForTemplate, '', $output);
+ $exit = Console::execute($gitCloneCommandForTemplate, '', $stdout, $stderr);
if ($exit !== 0) {
- throw new \Exception('Unable to clone code repository: ' . $output);
+ throw new \Exception('Unable to clone code repository: ' . $stderr);
}
// Ensure directories
- Console::execute('mkdir -p ' . \escapeshellarg($tmpTemplateDirectory . '/' . $templateRootDirectory), '', $output);
- Console::execute('mkdir -p ' . \escapeshellarg($tmpDirectory . '/' . $rootDirectory), '', $output);
+ Console::execute('mkdir -p ' . \escapeshellarg($tmpTemplateDirectory . '/' . $templateRootDirectory), '', $stdout, $stderr);
+ Console::execute('mkdir -p ' . \escapeshellarg($tmpDirectory . '/' . $rootDirectory), '', $stdout, $stderr);
// Merge template into user repo
- Console::execute('rsync -av --exclude \'.git\' ' . \escapeshellarg($tmpTemplateDirectory . '/' . $templateRootDirectory . '/') . ' ' . \escapeshellarg($tmpDirectory . '/' . $rootDirectory), '', $output);
+ Console::execute('rsync -av --exclude \'.git\' ' . \escapeshellarg($tmpTemplateDirectory . '/' . $templateRootDirectory . '/') . ' ' . \escapeshellarg($tmpDirectory . '/' . $rootDirectory), '', $stdout, $stderr);
// Commit and push
- $exit = Console::execute('git config --global user.email "team@appwrite.io" && git config --global user.name "Appwrite" && cd ' . \escapeshellarg($tmpDirectory) . ' && git add . && git commit -m "Create ' . \escapeshellarg($function->getAttribute('name', '')) . ' function" && git push origin ' . \escapeshellarg($branchName), '', $output);
+ $exit = Console::execute('git config --global user.email "team@appwrite.io" && git config --global user.name "Appwrite" && cd ' . \escapeshellarg($tmpDirectory) . ' && git add . && git commit -m "Create ' . \escapeshellarg($function->getAttribute('name', '')) . ' function" && git push origin ' . \escapeshellarg($branchName), '', $stdout, $stderr);
if ($exit !== 0) {
- throw new \Exception('Unable to push code repository: ' . $output);
+ throw new \Exception('Unable to push code repository: ' . $stderr);
}
- $exit = Console::execute('cd ' . \escapeshellarg($tmpDirectory) . ' && git rev-parse HEAD', '', $output);
+ $exit = Console::execute('cd ' . \escapeshellarg($tmpDirectory) . ' && git rev-parse HEAD', '', $stdout, $stderr);
if ($exit !== 0) {
- throw new \Exception('Unable to get vcs commit SHA: ' . $output);
+ throw new \Exception('Unable to get vcs commit SHA: ' . $stderr);
}
- $providerCommitHash = \trim($output);
+ $providerCommitHash = \trim($stdout);
$authorUrl = "https://github.com/$cloneOwner";
$deployment->setAttribute('providerCommitHash', $providerCommitHash ?? '');
@@ -397,7 +399,7 @@ class Builds extends Action
}
$tarParamDirectory = '/tmp/builds/' . $buildId . '/code' . (empty($rootDirectory) ? '' : '/' . $rootDirectory);
- Console::execute('tar --exclude code.tar.gz -czf ' . \escapeshellarg($tmpPathFile) . ' -C ' . \escapeshellcmd($tarParamDirectory) . ' .', '', $output); // TODO: Replace escapeshellcmd with escapeshellarg if we find a way that doesnt break syntax
+ Console::execute('tar --exclude code.tar.gz -czf ' . \escapeshellarg($tmpPathFile) . ' -C ' . \escapeshellcmd($tarParamDirectory) . ' .', '', $stdout, $stderr); // TODO: Replace escapeshellcmd with escapeshellarg if we find a way that doesnt break syntax
$source = $deviceForFunctions->getPath($deployment->getId() . '.' . \pathinfo('code.tar.gz', PATHINFO_EXTENSION));
$result = $localDevice->transfer($tmpPathFile, $source, $deviceForFunctions);
@@ -406,7 +408,7 @@ class Builds extends Action
throw new \Exception("Unable to move file");
}
- Console::execute('rm -rf ' . \escapeshellarg($tmpPath), '', $output);
+ Console::execute('rm -rf ' . \escapeshellarg($tmpPath), '', $stdout, $stderr);
$build = $dbForProject->updateDocument('builds', $build->getId(), $build->setAttribute('source', $source));
@@ -661,7 +663,7 @@ class Builds extends Action
->setAttribute('resourceUpdatedAt', DateTime::now())
->setAttribute('schedule', $function->getAttribute('schedule'))
->setAttribute('active', !empty($function->getAttribute('schedule')) && !empty($function->getAttribute('deployment')));
- $auth->skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule));
+ Authorization::skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule));
} catch (\Throwable $th) {
if ($dbForProject->getDocument('builds', $buildId)->getAttribute('status') === 'canceled') {
Console::info('Build has been canceled');
diff --git a/src/Appwrite/Platform/Workers/Certificates.php b/src/Appwrite/Platform/Workers/Certificates.php
index 396977f1ba..f25a85fb10 100644
--- a/src/Appwrite/Platform/Workers/Certificates.php
+++ b/src/Appwrite/Platform/Workers/Certificates.php
@@ -11,6 +11,7 @@ use Appwrite\Template\Template;
use Appwrite\Utopia\Response\Model\Rule;
use Exception;
use Throwable;
+use Utopia\App;
use Utopia\CLI\Console;
use Utopia\Database\Database;
use Utopia\Database\DateTime;
@@ -21,7 +22,6 @@ use Utopia\Database\Exception\Structure;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Query;
use Utopia\Domains\Domain;
-use Utopia\Http\Http;
use Utopia\Locale\Locale;
use Utopia\Logger\Log;
use Utopia\Platform\Action;
@@ -330,26 +330,30 @@ class Certificates extends Action
*
* @param string $folder Folder into which certificates should be generated
* @param string $domain Domain to generate certificate for
- * @return string output
+ * @return array Named array with keys 'stdout' and 'stderr', both string
* @throws Exception
*/
- private function issueCertificate(string $folder, string $domain, string $email): string
+ private function issueCertificate(string $folder, string $domain, string $email): array
{
- $output = '';
+ $stdout = '';
+ $stderr = '';
- $staging = (Http::isProduction()) ? '' : ' --dry-run';
+ $staging = (App::isProduction()) ? '' : ' --dry-run';
$exit = Console::execute("certbot certonly -v --webroot --noninteractive --agree-tos{$staging}"
. " --email " . $email
. " --cert-name " . $folder
. " -w " . APP_STORAGE_CERTIFICATES
- . " -d {$domain}", '', $output);
+ . " -d {$domain}", '', $stdout, $stderr);
// Unexpected error, usually 5XX, API limits, ...
if ($exit !== 0) {
- throw new Exception('Failed to issue a certificate with message: ' . $output);
+ throw new Exception('Failed to issue a certificate with message: ' . $stderr);
}
- return $output;
+ return [
+ 'stdout' => $stdout,
+ 'stderr' => $stderr
+ ];
}
/**
@@ -377,7 +381,7 @@ class Certificates extends Action
* @return void
* @throws Exception
*/
- private function applyCertificateFiles(string $folder, string $domain, string $letsEncryptData): void
+ private function applyCertificateFiles(string $folder, string $domain, array $letsEncryptData): void
{
// Prepare folder in storage for domain
@@ -390,19 +394,19 @@ class Certificates extends Action
// Move generated files
if (!@\rename('/etc/letsencrypt/live/' . $folder . '/cert.pem', APP_STORAGE_CERTIFICATES . '/' . $domain . '/cert.pem')) {
- throw new Exception('Failed to rename certificate cert.pem. Let\'s Encrypt log: ' . $letsEncryptData);
+ throw new Exception('Failed to rename certificate cert.pem. Let\'s Encrypt log: ' . $letsEncryptData['stderr'] . ' ; ' . $letsEncryptData['stdout']);
}
if (!@\rename('/etc/letsencrypt/live/' . $folder . '/chain.pem', APP_STORAGE_CERTIFICATES . '/' . $domain . '/chain.pem')) {
- throw new Exception('Failed to rename certificate chain.pem. Let\'s Encrypt log: ' . $letsEncryptData);
+ throw new Exception('Failed to rename certificate chain.pem. Let\'s Encrypt log: ' . $letsEncryptData['stderr'] . ' ; ' . $letsEncryptData['stdout']);
}
if (!@\rename('/etc/letsencrypt/live/' . $folder . '/fullchain.pem', APP_STORAGE_CERTIFICATES . '/' . $domain . '/fullchain.pem')) {
- throw new Exception('Failed to rename certificate fullchain.pem. Let\'s Encrypt log: ' . $letsEncryptData);
+ throw new Exception('Failed to rename certificate fullchain.pem. Let\'s Encrypt log: ' . $letsEncryptData['stderr'] . ' ; ' . $letsEncryptData['stdout']);
}
if (!@\rename('/etc/letsencrypt/live/' . $folder . '/privkey.pem', APP_STORAGE_CERTIFICATES . '/' . $domain . '/privkey.pem')) {
- throw new Exception('Failed to rename certificate privkey.pem. Let\'s Encrypt log: ' . $letsEncryptData);
+ throw new Exception('Failed to rename certificate privkey.pem. Let\'s Encrypt log: ' . $letsEncryptData['stderr'] . ' ; ' . $letsEncryptData['stdout']);
}
$config = \implode(PHP_EOL, [
diff --git a/src/Appwrite/Platform/Workers/Deletes.php b/src/Appwrite/Platform/Workers/Deletes.php
index e0d3bc8405..62fd8cd177 100644
--- a/src/Appwrite/Platform/Workers/Deletes.php
+++ b/src/Appwrite/Platform/Workers/Deletes.php
@@ -22,7 +22,6 @@ use Utopia\Database\Exception\Conflict;
use Utopia\Database\Exception\Restricted;
use Utopia\Database\Exception\Structure;
use Utopia\Database\Query;
-use Utopia\Database\Validator\Authorization as ValidatorAuthorization;
use Utopia\DSN\DSN;
use Utopia\Logger\Log;
use Utopia\Platform\Action;
@@ -55,15 +54,14 @@ class Deletes extends Action
->inject('executionRetention')
->inject('auditRetention')
->inject('log')
- ->inject('authorization')
- ->callback(fn ($message, $dbForConsole, callable $getProjectDB, Device $deviceForFiles, Device $deviceForFunctions, Device $deviceForBuilds, Device $deviceForCache, string $abuseRetention, string $executionRetention, string $auditRetention, Log $log, ValidatorAuthorization $authorization) => $this->action($message, $dbForConsole, $getProjectDB, $deviceForFiles, $deviceForFunctions, $deviceForBuilds, $deviceForCache, $abuseRetention, $executionRetention, $auditRetention, $log, $authorization));
+ ->callback(fn ($message, $dbForConsole, callable $getProjectDB, Device $deviceForFiles, Device $deviceForFunctions, Device $deviceForBuilds, Device $deviceForCache, string $abuseRetention, string $executionRetention, string $auditRetention, Log $log) => $this->action($message, $dbForConsole, $getProjectDB, $deviceForFiles, $deviceForFunctions, $deviceForBuilds, $deviceForCache, $abuseRetention, $executionRetention, $auditRetention, $log));
}
/**
* @throws Exception
* @throws Throwable
*/
- public function action(Message $message, Database $dbForConsole, callable $getProjectDB, Device $deviceForFiles, Device $deviceForFunctions, Device $deviceForBuilds, Device $deviceForCache, string $abuseRetention, string $executionRetention, string $auditRetention, Log $log, ValidatorAuthorization $auth): void
+ public function action(Message $message, Database $dbForConsole, callable $getProjectDB, Device $deviceForFiles, Device $deviceForFunctions, Device $deviceForBuilds, Device $deviceForCache, string $abuseRetention, string $executionRetention, string $auditRetention, Log $log): void
{
$payload = $message->getPayload() ?? [];
@@ -119,7 +117,7 @@ class Deletes extends Action
break;
case DELETE_TYPE_AUDIT:
if (!$project->isEmpty()) {
- $this->deleteAuditLogs($project, $getProjectDB, $auditRetention, $auth);
+ $this->deleteAuditLogs($project, $getProjectDB, $auditRetention);
}
if (!$document->isEmpty()) {
@@ -127,7 +125,7 @@ class Deletes extends Action
}
break;
case DELETE_TYPE_ABUSE:
- $this->deleteAbuseLogs($project, $getProjectDB, $abuseRetention, $auth);
+ $this->deleteAbuseLogs($project, $getProjectDB, $abuseRetention);
break;
case DELETE_TYPE_REALTIME:
$this->deleteRealtimeUsage($dbForConsole, $datetime);
@@ -482,6 +480,7 @@ class Deletes extends Action
private function deleteProject(Database $dbForConsole, callable $getProjectDB, Device $deviceForFiles, Device $deviceForFunctions, Device $deviceForBuilds, Device $deviceForCache, Document $document): void
{
$projectInternalId = $document->getInternalId();
+ $projectId = $document->getId();
try {
$dsn = new DSN($document->getAttribute('database', 'console'));
@@ -505,7 +504,18 @@ class Deletes extends Action
foreach ($collections as $collection) {
if ($dsn->getHost() !== System::getEnv('_APP_DATABASE_SHARED_TABLES', '') || !\in_array($collection->getId(), $projectCollectionIds)) {
- $dbForProject->deleteCollection($collection->getId());
+ try {
+ $dbForProject->deleteCollection($collection->getId());
+ } catch (Throwable $e) {
+ Console::error('Error deleting '.$collection->getId().' '.$e->getMessage());
+
+ /**
+ * Ignore junction tables;
+ */
+ if (!preg_match('/^_\d+_\d+$/', $collection->getId())) {
+ throw $e;
+ }
+ }
} else {
$this->deleteByGroup($collection->getId(), [], database: $dbForProject);
}
@@ -559,6 +569,11 @@ class Deletes extends Action
Query::equal('projectInternalId', [$projectInternalId]),
], $dbForConsole);
+ // Delete Schedules (No projectInternalId in this collection)
+ $this->deleteByGroup('schedules', [
+ Query::equal('projectId', [$projectId]),
+ ], $dbForConsole);
+
// Delete metadata table
if ($dsn->getHost() !== System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) {
$dbForProject->deleteCollection('_metadata');
@@ -684,7 +699,7 @@ class Deletes extends Action
* @return void
* @throws Exception
*/
- private function deleteAbuseLogs(Document $project, callable $getProjectDB, string $abuseRetention, ValidatorAuthorization $auth): void
+ private function deleteAbuseLogs(Document $project, callable $getProjectDB, string $abuseRetention): void
{
$projectId = $project->getId();
$dbForProject = $getProjectDB($project);
@@ -705,7 +720,7 @@ class Deletes extends Action
* @return void
* @throws Exception
*/
- private function deleteAuditLogs(Document $project, callable $getProjectDB, string $auditRetention, ValidatorAuthorization $auth): void
+ private function deleteAuditLogs(Document $project, callable $getProjectDB, string $auditRetention): void
{
$projectId = $project->getId();
$dbForProject = $getProjectDB($project);
@@ -957,7 +972,7 @@ class Deletes extends Action
* @return void
* @throws Exception
*/
- private function deleteByGroup(string $collection, array $queries, Database $database, callable $callback = null): void
+ protected function deleteByGroup(string $collection, array $queries, Database $database, callable $callback = null): void
{
$count = 0;
$chunk = 0;
@@ -999,7 +1014,7 @@ class Deletes extends Action
* @return void
* @throws Exception
*/
- private function listByGroup(string $collection, array $queries, Database $database, callable $callback = null): void
+ protected function listByGroup(string $collection, array $queries, Database $database, callable $callback = null): void
{
$count = 0;
$chunk = 0;
diff --git a/src/Appwrite/Platform/Workers/Functions.php b/src/Appwrite/Platform/Workers/Functions.php
index e0f66c30f9..dfe7435426 100644
--- a/src/Appwrite/Platform/Workers/Functions.php
+++ b/src/Appwrite/Platform/Workers/Functions.php
@@ -71,12 +71,6 @@ class Functions extends Action
throw new Exception('Missing payload');
}
- $payload = $message->getPayload() ?? [];
-
- if (empty($payload)) {
- throw new Exception('Missing payload');
- }
-
$type = $payload['type'] ?? '';
$events = $payload['events'] ?? [];
$data = $payload['body'] ?? '';
@@ -85,9 +79,23 @@ class Functions extends Action
$function = new Document($payload['function'] ?? []);
$functionId = $payload['functionId'] ?? '';
$user = new Document($payload['user'] ?? []);
+ $userId = $payload['userId'] ?? '';
$method = $payload['method'] ?? 'POST';
$headers = $payload['headers'] ?? [];
$path = $payload['path'] ?? '/';
+ $jwt = $payload['jwt'] ?? '';
+
+ if ($user->isEmpty() && !empty($userId)) {
+ $user = $dbForProject->getDocument('users', $userId);
+ }
+
+ if (empty($jwt) && !$user->isEmpty()) {
+ $jwtExpiry = $function->getAttribute('timeout', 900);
+ $jwtObj = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', $jwtExpiry, 0);
+ $jwt = $jwtObj->encode([
+ 'userId' => $user->getId(),
+ ]);
+ }
if ($project->getId() === 'console') {
return;
@@ -157,7 +165,6 @@ class Functions extends Action
*/
switch ($type) {
case 'http':
- $jwt = $payload['jwt'] ?? '';
$execution = new Document($payload['execution'] ?? []);
$user = new Document($payload['user'] ?? []);
$this->execute(
@@ -194,9 +201,9 @@ class Functions extends Action
path: $path,
method: $method,
headers: $headers,
- data: null,
- user: null,
- jwt: null,
+ data: $data,
+ user: $user,
+ jwt: $jwt,
event: null,
eventData: null,
executionId: $execution->getId() ?? null
@@ -580,7 +587,8 @@ class Functions extends Action
$target = Realtime::fromPayload(
// Pass first, most verbose event pattern
event: $allEvents[0],
- payload: $execution
+ payload: $execution,
+ project: $project
);
Realtime::send(
projectId: 'console',
diff --git a/src/Appwrite/Platform/Workers/Mails.php b/src/Appwrite/Platform/Workers/Mails.php
index 51bdb56cf0..e48f96ea90 100644
--- a/src/Appwrite/Platform/Workers/Mails.php
+++ b/src/Appwrite/Platform/Workers/Mails.php
@@ -84,13 +84,13 @@ class Mails extends Action
$bodyTemplate = __DIR__ . '/../../../../app/config/locale/templates/email-base.tpl';
}
$bodyTemplate = Template::fromFile($bodyTemplate);
- $bodyTemplate->setParam('{{body}}', $body, escape: false);
+ $bodyTemplate->setParam('{{body}}', $body, escapeHtml: false);
foreach ($variables as $key => $value) {
// TODO: hotfix for redirect param
- $bodyTemplate->setParam('{{' . $key . '}}', $value, escape: $key !== 'redirect');
+ $bodyTemplate->setParam('{{' . $key . '}}', $value, escapeHtml: $key !== 'redirect');
}
foreach ($this->richTextParams as $key => $value) {
- $bodyTemplate->setParam('{{' . $key . '}}', $value, escape: false);
+ $bodyTemplate->setParam('{{' . $key . '}}', $value, escapeHtml: false);
}
$body = $bodyTemplate->render();
diff --git a/src/Appwrite/Platform/Workers/Messaging.php b/src/Appwrite/Platform/Workers/Messaging.php
index 271bbfedf1..510fec0431 100644
--- a/src/Appwrite/Platform/Workers/Messaging.php
+++ b/src/Appwrite/Platform/Workers/Messaging.php
@@ -101,7 +101,7 @@ class Messaging extends Action
case MESSAGE_SEND_TYPE_EXTERNAL:
$message = $dbForProject->getDocument('messages', $payload['messageId']);
- $this->sendExternalMessage($dbForProject, $message, $deviceForFiles, $project);
+ $this->sendExternalMessage($dbForProject, $message, $deviceForFiles, $project, $queueForUsage);
break;
default:
throw new \Exception('Unknown message type: ' . $type);
@@ -113,6 +113,7 @@ class Messaging extends Action
Document $message,
Device $deviceForFiles,
Document $project,
+ Usage $queueForUsage
): void {
$topicIds = $message->getAttribute('topics', []);
$targetIds = $message->getAttribute('targets', []);
@@ -218,8 +219,8 @@ class Messaging extends Action
/**
* @var array $results
*/
- $results = batch(\array_map(function ($providerId) use ($identifiers, &$providers, $default, $message, $dbForProject, $deviceForFiles, $project) {
- return function () use ($providerId, $identifiers, &$providers, $default, $message, $dbForProject, $deviceForFiles, $project) {
+ $results = batch(\array_map(function ($providerId) use ($identifiers, &$providers, $default, $message, $dbForProject, $deviceForFiles, $project, $queueForUsage) {
+ return function () use ($providerId, $identifiers, &$providers, $default, $message, $dbForProject, $deviceForFiles, $project, $queueForUsage) {
if (\array_key_exists($providerId, $providers)) {
$provider = $providers[$providerId];
} else {
@@ -246,8 +247,8 @@ class Messaging extends Action
$adapter->getMaxMessagesPerRequest()
);
- return batch(\array_map(function ($batch) use ($message, $provider, $adapter, $dbForProject, $deviceForFiles, $project) {
- return function () use ($batch, $message, $provider, $adapter, $dbForProject, $deviceForFiles, $project) {
+ return batch(\array_map(function ($batch) use ($message, $provider, $adapter, $dbForProject, $deviceForFiles, $project, $queueForUsage) {
+ return function () use ($batch, $message, $provider, $adapter, $dbForProject, $deviceForFiles, $project, $queueForUsage) {
$deliveredTotal = 0;
$deliveryErrors = [];
$messageData = clone $message;
@@ -286,6 +287,20 @@ class Messaging extends Action
} catch (\Throwable $e) {
$deliveryErrors[] = 'Failed sending to targets with error: ' . $e->getMessage();
} finally {
+ $errorTotal = count($deliveryErrors);
+ $queueForUsage
+ ->setProject($project)
+ ->addMetric(METRIC_MESSAGES, ($deliveredTotal + $errorTotal))
+ ->addMetric(METRIC_MESSAGES_SENT, $deliveredTotal)
+ ->addMetric(METRIC_MESSAGES_FAILED, $errorTotal)
+ ->addMetric(str_replace('{type}', $provider->getAttribute('type'), METRIC_MESSAGES_TYPE), ($deliveredTotal + $errorTotal))
+ ->addMetric(str_replace('{type}', $provider->getAttribute('type'), METRIC_MESSAGES_TYPE_SENT), $deliveredTotal)
+ ->addMetric(str_replace('{type}', $provider->getAttribute('type'), METRIC_MESSAGES_TYPE_FAILED), $errorTotal)
+ ->addMetric(str_replace(['{type}', '{provider}'], [$provider->getAttribute('type'), $provider->getAttribute('provider')], METRIC_MESSAGES_TYPE_PROVIDER), ($deliveredTotal + $errorTotal))
+ ->addMetric(str_replace(['{type}', '{provider}'], [$provider->getAttribute('type'), $provider->getAttribute('provider')], METRIC_MESSAGES_TYPE_PROVIDER_SENT), $deliveredTotal)
+ ->addMetric(str_replace(['{type}', '{provider}'], [$provider->getAttribute('type'), $provider->getAttribute('provider')], METRIC_MESSAGES_TYPE_PROVIDER_FAILED), $errorTotal)
+ ->trigger();
+
return [
'deliveredTotal' => $deliveredTotal,
'deliveryErrors' => $deliveryErrors,
@@ -318,6 +333,7 @@ class Messaging extends Action
$message->setAttribute('status', MessageStatus::SENT);
}
+
$message->removeAttribute('to');
foreach ($providers as $provider) {
@@ -473,11 +489,29 @@ class Messaging extends Action
return match ($provider->getAttribute('provider')) {
'mock' => new Mock('username', 'password'),
- 'twilio' => new Twilio($credentials['accountSid'], $credentials['authToken'], null, isset($credentials['messagingServiceSid']) ? $credentials['messagingServiceSid'] : null),
- 'textmagic' => new TextMagic($credentials['username'], $credentials['apiKey']),
- 'telesign' => new Telesign($credentials['customerId'], $credentials['apiKey']),
- 'msg91' => new Msg91($credentials['senderId'], $credentials['authKey'], $credentials['templateId']),
- 'vonage' => new Vonage($credentials['apiKey'], $credentials['apiSecret']),
+ 'twilio' => new Twilio(
+ $credentials['accountSid'] ?? '',
+ $credentials['authToken'] ?? '',
+ null,
+ $credentials['messagingServiceSid'] ?? null
+ ),
+ 'textmagic' => new TextMagic(
+ $credentials['username'] ?? '',
+ $credentials['apiKey'] ?? ''
+ ),
+ 'telesign' => new Telesign(
+ $credentials['customerId'] ?? '',
+ $credentials['apiKey'] ?? ''
+ ),
+ 'msg91' => new Msg91(
+ $credentials['senderId'] ?? '',
+ $credentials['authKey'] ?? '',
+ $credentials['templateId'] ?? ''
+ ),
+ 'vonage' => new Vonage(
+ $credentials['apiKey'] ?? '',
+ $credentials['apiSecret'] ?? ''
+ ),
default => null
};
}
@@ -490,11 +524,11 @@ class Messaging extends Action
return match ($provider->getAttribute('provider')) {
'mock' => new Mock('username', 'password'),
'apns' => new APNS(
- $credentials['authKey'],
- $credentials['authKeyId'],
- $credentials['teamId'],
- $credentials['bundleId'],
- $options['sandbox']
+ $credentials['authKey'] ?? '',
+ $credentials['authKeyId'] ?? '',
+ $credentials['teamId'] ?? '',
+ $credentials['bundleId'] ?? '',
+ $options['sandbox'] ?? false
),
'fcm' => new FCM(\json_encode($credentials['serviceAccountJSON'])),
default => null
@@ -505,24 +539,25 @@ class Messaging extends Action
{
$credentials = $provider->getAttribute('credentials', []);
$options = $provider->getAttribute('options', []);
+ $apiKey = $credentials['apiKey'] ?? '';
return match ($provider->getAttribute('provider')) {
'mock' => new Mock('username', 'password'),
'smtp' => new SMTP(
- $credentials['host'],
- $credentials['port'],
- $credentials['username'],
- $credentials['password'],
- $options['encryption'],
- $options['autoTLS'],
- $options['mailer'],
+ $credentials['host'] ?? '',
+ $credentials['port'] ?? 25,
+ $credentials['username'] ?? '',
+ $credentials['password'] ?? '',
+ $options['encryption'] ?? '',
+ $options['autoTLS'] ?? false,
+ $options['mailer'] ?? '',
),
'mailgun' => new Mailgun(
- $credentials['apiKey'],
- $credentials['domain'],
- $credentials['isEuRegion']
+ $apiKey,
+ $credentials['domain'] ?? '',
+ $credentials['isEuRegion'] ?? false
),
- 'sendgrid' => new Sendgrid($credentials['apiKey']),
+ 'sendgrid' => new Sendgrid($apiKey),
default => null
};
}
diff --git a/src/Appwrite/Platform/Workers/Migrations.php b/src/Appwrite/Platform/Workers/Migrations.php
index 8ab5ebac46..c25d1b59e4 100644
--- a/src/Appwrite/Platform/Workers/Migrations.php
+++ b/src/Appwrite/Platform/Workers/Migrations.php
@@ -8,6 +8,7 @@ use Appwrite\Permission;
use Appwrite\Role;
use Exception;
use Utopia\CLI\Console;
+use Utopia\Config\Config;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Exception\Authorization;
@@ -16,11 +17,11 @@ use Utopia\Database\Exception\Restricted;
use Utopia\Database\Exception\Structure;
use Utopia\Database\Helpers\ID;
use Utopia\Logger\Log;
-use Utopia\Logger\Log\Breadcrumb;
-use Utopia\Migration\Destinations\Appwrite as DestinationsAppwrite;
+use Utopia\Migration\Destination;
+use Utopia\Migration\Destinations\Appwrite as DestinationAppwrite;
use Utopia\Migration\Exception as MigrationException;
use Utopia\Migration\Source;
-use Utopia\Migration\Sources\Appwrite;
+use Utopia\Migration\Sources\Appwrite as SourceAppwrite;
use Utopia\Migration\Sources\Firebase;
use Utopia\Migration\Sources\NHost;
use Utopia\Migration\Sources\Supabase;
@@ -30,8 +31,11 @@ use Utopia\Queue\Message;
class Migrations extends Action
{
- private ?Database $dbForProject = null;
- private ?Database $dbForConsole = null;
+ protected Database $dbForProject;
+
+ protected Database $dbForConsole;
+
+ protected Document $project;
public static function getName(): string
{
@@ -53,11 +57,6 @@ class Migrations extends Action
}
/**
- * @param Message $message
- * @param Database $dbForProject
- * @param Database $dbForConsole
- * @param Log $log
- * @return void
* @throws Exception
*/
public function action(Message $message, Database $dbForProject, Database $dbForConsole, Log $log): void
@@ -78,6 +77,7 @@ class Migrations extends Action
$this->dbForProject = $dbForProject;
$this->dbForConsole = $dbForConsole;
+ $this->project = $project;
/**
* Handle Event execution.
@@ -89,17 +89,17 @@ class Migrations extends Action
$log->addTag('migrationId', $migration->getId());
$log->addTag('projectId', $project->getId());
- $this->processMigration($project, $migration, $log);
+ $this->processMigration($migration, $log);
}
/**
- * @param string $source
- * @param array $credentials
- * @return Source
* @throws Exception
*/
- protected function processSource(string $source, array $credentials): Source
+ protected function processSource(Document $migration): Source
{
+ $source = $migration->getAttribute('source');
+ $credentials = $migration->getAttribute('credentials');
+
return match ($source) {
Firebase::getName() => new Firebase(
json_decode($credentials['serviceAccount'], true),
@@ -122,11 +122,35 @@ class Migrations extends Action
$credentials['password'],
$credentials['port'],
),
- Appwrite::getName() => new Appwrite($credentials['projectId'], str_starts_with($credentials['endpoint'], 'http://localhost/v1') ? 'http://appwrite/v1' : $credentials['endpoint'], $credentials['apiKey']),
+ SourceAppwrite::getName() => new SourceAppwrite(
+ $credentials['projectId'],
+ $credentials['endpoint'],
+ $credentials['apiKey'],
+ ),
default => throw new \Exception('Invalid source type'),
};
}
+ /**
+ * @throws Exception
+ */
+ protected function processDestination(Document $migration): Destination
+ {
+ $destination = $migration->getAttribute('destination');
+ $credentials = $migration->getAttribute('credentials');
+
+ return match ($destination) {
+ DestinationAppwrite::getName() => new DestinationAppwrite(
+ $credentials['projectId'],
+ $credentials['endpoint'],
+ $credentials['apiKey'],
+ $this->dbForProject,
+ Config::getParam('collections', [])['databases']['collections'],
+ ),
+ default => throw new \Exception('Invalid destination type'),
+ };
+ }
+
/**
* @throws Authorization
* @throws Structure
@@ -167,8 +191,6 @@ class Migrations extends Action
}
/**
- * @param Document $apiKey
- * @return void
* @throws \Utopia\Database\Exception
* @throws Authorization
* @throws Conflict
@@ -181,8 +203,6 @@ class Migrations extends Action
}
/**
- * @param Document $project
- * @return Document
* @throws Authorization
* @throws Structure
* @throws \Utopia\Database\Exception
@@ -233,99 +253,116 @@ class Migrations extends Action
}
/**
- * @param Document $project
- * @param Document $migration
- * @param Log $log
- * @return void
* @throws Authorization
* @throws Conflict
* @throws Restricted
* @throws Structure
* @throws \Utopia\Database\Exception
+ * @throws Exception
*/
- protected function processMigration(Document $project, Document $migration, Log $log): void
+ protected function processMigration(Document $migration, Log $log): void
{
- /**
- * @var Document $migrationDocument
- * @var Transfer $transfer
- */
- $migrationDocument = null;
- $transfer = null;
+ $project = $this->project;
$projectDocument = $this->dbForConsole->getDocument('projects', $project->getId());
$tempAPIKey = $this->generateAPIKey($projectDocument);
+ $transfer = $source = $destination = null;
+
try {
- $migrationDocument = $this->dbForProject->getDocument('migrations', $migration->getId());
- $migrationDocument->setAttribute('stage', 'processing');
- $migrationDocument->setAttribute('status', 'processing');
- $log->addBreadcrumb(new Breadcrumb("debug", "migration", "Migration hit stage 'processing'", \microtime(true)));
- $this->updateMigrationDocument($migrationDocument, $projectDocument);
+ $migration = $this->dbForProject->getDocument('migrations', $migration->getId());
- $log->addTag('type', $migrationDocument->getAttribute('source'));
+ if (
+ $migration->getAttribute('source') === SourceAppwrite::getName() ||
+ $migration->getAttribute('destination') === DestinationAppwrite::getName()
+ ) {
+ $credentials = $migration->getAttribute('credentials', []);
- $source = $this->processSource($migrationDocument->getAttribute('source'), $migrationDocument->getAttribute('credentials'));
+ $credentials['projectId'] = $credentials['projectId'] ?? $projectDocument->getId();
+ $credentials['endpoint'] = $credentials['endpoint'] ?? 'http://appwrite/v1';
+ $credentials['apiKey'] = $credentials['apiKey'] ?? $tempAPIKey['secret'];
+
+ $migration->setAttribute('credentials', $credentials);
+ }
+
+ $migration->setAttribute('stage', 'processing');
+ $migration->setAttribute('status', 'processing');
+ $this->updateMigrationDocument($migration, $projectDocument);
+
+ $log->addTag('type', $migration->getAttribute('source'));
+
+ $source = $this->processSource($migration);
+ $destination = $this->processDestination($migration);
$source->report();
- $destination = new DestinationsAppwrite(
- $projectDocument->getId(),
- 'http://appwrite/v1',
- $tempAPIKey['secret'],
- );
-
$transfer = new Transfer(
$source,
$destination
);
/** Start Transfer */
- $migrationDocument->setAttribute('stage', 'migrating');
- $log->addBreadcrumb(new Breadcrumb("debug", "migration", "Migration hit stage 'migrating'", \microtime(true)));
- $this->updateMigrationDocument($migrationDocument, $projectDocument);
- $transfer->run($migrationDocument->getAttribute('resources'), function () use ($migrationDocument, $transfer, $projectDocument) {
- $migrationDocument->setAttribute('resourceData', json_encode($transfer->getCache()));
- $migrationDocument->setAttribute('statusCounters', json_encode($transfer->getStatusCounters()));
+ $migration->setAttribute('stage', 'migrating');
+ $this->updateMigrationDocument($migration, $projectDocument);
- $this->updateMigrationDocument($migrationDocument, $projectDocument);
- });
+ $transfer->run(
+ $migration->getAttribute('resources'),
+ function () use ($migration, $transfer, $projectDocument) {
+ $migration->setAttribute('resourceData', json_encode($transfer->getCache()));
+ $migration->setAttribute('statusCounters', json_encode($transfer->getStatusCounters()));
+ $this->updateMigrationDocument($migration, $projectDocument);
+ },
+ $migration->getAttribute('resourceId'),
+ $migration->getAttribute('resourceType')
+ );
+
+ $destination->shutDown();
+ $source->shutDown();
$sourceErrors = $source->getErrors();
$destinationErrors = $destination->getErrors();
- if (!empty($sourceErrors) || !empty($destinationErrors)) {
- $migrationDocument->setAttribute('status', 'failed');
- $migrationDocument->setAttribute('stage', 'finished');
- $log->addBreadcrumb(new Breadcrumb("debug", "migration", "Migration hit stage 'finished' and failed", \microtime(true)));
+ if (! empty($sourceErrors) || ! empty($destinationErrors)) {
+ $migration->setAttribute('status', 'failed');
+ $migration->setAttribute('stage', 'finished');
$errorMessages = [];
foreach ($sourceErrors as $error) {
- /** @var MigrationException $error */
- $errorMessages[] = "Error occurred while fetching '{$error->getResourceGroup()}:{$error->getResourceId()}' from source with message: '{$error->getMessage()}'";
+ /** @var $sourceErrors $error */
+ $message = "Error occurred while fetching '{$error->getResourceName()}:{$error->getResourceId()}' from source with message: '{$error->getMessage()}'";
+ if ($error->getPrevious()) {
+ $message .= " Message: ".$error->getPrevious()->getMessage() . " File: ".$error->getPrevious()->getFile() . " Line: ".$error->getPrevious()->getLine();
+ }
+
+ $errorMessages[] = $message;
}
foreach ($destinationErrors as $error) {
+ $message = "Error occurred while pushing '{$error->getResourceName()}:{$error->getResourceId()}' to destination with message: '{$error->getMessage()}'";
+
+ if ($error->getPrevious()) {
+ $message .= " Message: ".$error->getPrevious()->getMessage() . " File: ".$error->getPrevious()->getFile() . " Line: ".$error->getPrevious()->getLine();
+ }
+
/** @var MigrationException $error */
- $errorMessages[] = "Error occurred while pushing '{$error->getResourceGroup()}:{$error->getResourceId()}' to destination with message: '{$error->getMessage()}'";
+ $errorMessages[] = $message;
}
- $migrationDocument->setAttribute('errors', $errorMessages);
+ $migration->setAttribute('errors', $errorMessages);
$log->addExtra('migrationErrors', json_encode($errorMessages));
- $this->updateMigrationDocument($migrationDocument, $projectDocument);
+ $this->updateMigrationDocument($migration, $projectDocument);
return;
}
- $migrationDocument->setAttribute('status', 'completed');
- $migrationDocument->setAttribute('stage', 'finished');
- $log->addBreadcrumb(new Breadcrumb("debug", "migration", "Migration hit stage 'finished' and succeeded", \microtime(true)));
+ $migration->setAttribute('status', 'completed');
+ $migration->setAttribute('stage', 'finished');
} catch (\Throwable $th) {
Console::error($th->getMessage());
+ Console::error($th->getTraceAsString());
- if ($migrationDocument) {
- Console::error($th->getMessage());
- Console::error($th->getTraceAsString());
- $migrationDocument->setAttribute('status', 'failed');
- $migrationDocument->setAttribute('stage', 'finished');
- $migrationDocument->setAttribute('errors', [$th->getMessage()]);
+ if (! $migration->isEmpty()) {
+ $migration->setAttribute('status', 'failed');
+ $migration->setAttribute('stage', 'finished');
+ $migration->setAttribute('errors', [$th->getMessage()]);
return;
}
@@ -337,26 +374,30 @@ class Migrations extends Action
$errorMessages = [];
foreach ($sourceErrors as $error) {
/** @var MigrationException $error */
- $errorMessages[] = "Error occurred while fetching '{$error->getResourceGroup()}:{$error->getResourceId()}' from source with message '{$error->getMessage()}'";
+ $errorMessages[] = "Error occurred while fetching '{$error->getResourceName()}:{$error->getResourceId()}' from source with message '{$error->getMessage()}'";
}
foreach ($destinationErrors as $error) {
/** @var MigrationException $error */
- $errorMessages[] = "Error occurred while pushing '{$error->getResourceGroup()}:{$error->getResourceId()}' to destination with message '{$error->getMessage()}'";
+ $errorMessages[] = "Error occurred while pushing '{$error->getResourceName()}:{$error->getResourceId()}' to destination with message '{$error->getMessage()}'";
}
- $migrationDocument->setAttribute('errors', $errorMessages);
+ $migration->setAttribute('errors', $errorMessages);
$log->addTag('migrationErrors', json_encode($errorMessages));
}
} finally {
- if ($tempAPIKey) {
+ if (! $tempAPIKey->isEmpty()) {
$this->removeAPIKey($tempAPIKey);
}
- if ($migrationDocument) {
- $this->updateMigrationDocument($migrationDocument, $projectDocument);
- if ($migrationDocument->getAttribute('status', '') == 'failed') {
- throw new Exception("Migration failed");
- }
+ $this->updateMigrationDocument($migration, $projectDocument);
+
+ if ($migration->getAttribute('status', '') === 'failed') {
+ Console::error('Migration('.$migration->getInternalId().':'.$migration->getId().') failed, Project('.$this->project->getInternalId().':'.$this->project->getId().')');
+
+ $destination->error();
+ $source->error();
+
+ throw new Exception('Migration failed');
}
}
}
diff --git a/src/Appwrite/Platform/Workers/UsageDump.php b/src/Appwrite/Platform/Workers/UsageDump.php
index b7097dbb04..b5480e1dec 100644
--- a/src/Appwrite/Platform/Workers/UsageDump.php
+++ b/src/Appwrite/Platform/Workers/UsageDump.php
@@ -4,6 +4,7 @@ namespace Appwrite\Platform\Workers;
use Appwrite\Extend\Exception;
use Utopia\CLI\Console;
+use Utopia\Database\Database;
use Utopia\Database\DateTime;
use Utopia\Database\Document;
use Utopia\Database\Exception\Duplicate;
@@ -11,6 +12,10 @@ use Utopia\Platform\Action;
use Utopia\Queue\Message;
use Utopia\System\System;
+const METRIC_COLLECTION_LEVEL_STORAGE = 4;
+const METRIC_DATABASE_LEVEL_STORAGE = 3;
+const METRIC_PROJECT_LEVEL_STORAGE = 2;
+
class UsageDump extends Action
{
protected array $stats = [];
@@ -70,6 +75,15 @@ class UsageDump extends Action
continue;
}
+ if (str_contains($key, METRIC_DATABASES_STORAGE)) {
+ try {
+ $this->handleDatabaseStorage($key, $dbForProject);
+ } catch (\Exception $e) {
+ console::error('[' . DateTime::now() . '] failed to calculate database storage for key [' . $key . '] ' . $e->getMessage());
+ }
+ continue;
+ }
+
foreach ($this->periods as $period => $format) {
$time = 'inf' === $period ? null : date($format, time());
$id = \md5("{$time}_{$period}_{$key}");
@@ -107,4 +121,162 @@ class UsageDump extends Action
}
}
}
+
+ private function handleDatabaseStorage(string $key, Database $dbForProject): void
+ {
+ $data = explode('.', $key);
+ $start = microtime(true);
+
+ $updateMetric = function (Database $dbForProject, int $value, string $key, string $period, string|null $time) {
+ $id = \md5("{$time}_{$period}_{$key}");
+
+ try {
+ $dbForProject->createDocument('stats', new Document([
+ '$id' => $id,
+ 'period' => $period,
+ 'time' => $time,
+ 'metric' => $key,
+ 'value' => $value,
+ 'region' => System::getEnv('_APP_REGION', 'default'),
+ ]));
+ } catch (Duplicate $th) {
+ if ($value < 0) {
+ $dbForProject->decreaseDocumentAttribute(
+ 'stats',
+ $id,
+ 'value',
+ abs($value)
+ );
+ } else {
+ $dbForProject->increaseDocumentAttribute(
+ 'stats',
+ $id,
+ 'value',
+ $value
+ );
+ }
+ }
+ };
+
+ foreach ($this->periods as $period => $format) {
+ $time = 'inf' === $period ? null : date($format, time());
+ $id = \md5("{$time}_{$period}_{$key}");
+
+ $value = 0;
+ $previousValue = 0;
+ try {
+ $previousValue = ($dbForProject->getDocument('stats', $id))->getAttribute('value', 0);
+ } catch (\Exception $e) {
+ // No previous value
+ }
+
+ switch (count($data)) {
+ // Collection Level
+ case METRIC_COLLECTION_LEVEL_STORAGE:
+ Console::log('[' . DateTime::now() . '] Collection Level Storage Calculation [' . $key . ']');
+ $databaseInternalId = $data[0];
+ $collectionInternalId = $data[1];
+
+ try {
+ $value = $dbForProject->getSizeOfCollection('database_' . $databaseInternalId . '_collection_' . $collectionInternalId);
+ } catch (\Exception $e) {
+ // Collection not found
+ if ($e->getMessage() !== 'Collection not found') {
+ throw $e;
+ }
+ }
+
+ // Compare with previous value
+ $diff = $value - $previousValue;
+
+ if ($diff === 0) {
+ break;
+ }
+
+ // Update Collection
+ $updateMetric($dbForProject, $diff, $key, $period, $time);
+
+ // Update Database
+ $databaseKey = str_replace(['{databaseInternalId}'], [$data[0]], METRIC_DATABASE_ID_STORAGE);
+ $updateMetric($dbForProject, $diff, $databaseKey, $period, $time);
+
+ // Update Project
+ $projectKey = METRIC_DATABASES_STORAGE;
+ $updateMetric($dbForProject, $diff, $projectKey, $period, $time);
+ break;
+ // Database Level
+ case METRIC_DATABASE_LEVEL_STORAGE:
+ Console::log('[' . DateTime::now() . '] Database Level Storage Calculation [' . $key . ']');
+ $databaseInternalId = $data[0];
+
+ $collections = [];
+ try {
+ $collections = $dbForProject->find('database_' . $databaseInternalId);
+ } catch (\Exception $e) {
+ // Database not found
+ if ($e->getMessage() !== 'Collection not found') {
+ throw $e;
+ }
+ }
+
+ foreach ($collections as $collection) {
+ try {
+ $value += $dbForProject->getSizeOfCollection('database_' . $databaseInternalId . '_collection_' . $collection->getInternalId());
+ } catch (\Exception $e) {
+ // Collection not found
+ if ($e->getMessage() !== 'Collection not found') {
+ throw $e;
+ }
+ }
+ }
+
+ $diff = $value - $previousValue;
+
+ if ($diff === 0) {
+ break;
+ }
+
+ // Update Database
+ $databaseKey = str_replace(['{databaseInternalId}'], [$data[0]], METRIC_DATABASE_ID_STORAGE);
+ $updateMetric($dbForProject, $diff, $databaseKey, $period, $time);
+
+ // Update Project
+ $projectKey = METRIC_DATABASES_STORAGE;
+ $updateMetric($dbForProject, $diff, $projectKey, $period, $time);
+ break;
+ // Project Level
+ case METRIC_PROJECT_LEVEL_STORAGE:
+ Console::log('[' . DateTime::now() . '] Project Level Storage Calculation [' . $key . ']');
+ // Get all project databases
+ $databases = $dbForProject->find('database');
+
+ // Recalculate all databases
+ foreach ($databases as $database) {
+ $collections = $dbForProject->find('database_' . $database->getInternalId());
+
+ foreach ($collections as $collection) {
+ try {
+ $value += $dbForProject->getSizeOfCollection('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId());
+ } catch (\Exception $e) {
+ // Collection not found
+ if ($e->getMessage() !== 'Collection not found') {
+ throw $e;
+ }
+ }
+ }
+ }
+
+ $diff = $value - $previousValue;
+
+ // Update Project
+ $projectKey = METRIC_DATABASES_STORAGE;
+ $updateMetric($dbForProject, $diff, $projectKey, $period, $time);
+ break;
+ }
+ }
+
+ $end = microtime(true);
+
+ console::log('[' . DateTime::now() . '] DB Storage Calculation [' . $key . '] took ' . (($end - $start) * 1000) . ' milliseconds');
+ }
}
diff --git a/src/Appwrite/Promises/Promise.php b/src/Appwrite/Promises/Promise.php
index f12590dfed..a6b1aa79d5 100644
--- a/src/Appwrite/Promises/Promise.php
+++ b/src/Appwrite/Promises/Promise.php
@@ -12,7 +12,7 @@ abstract class Promise
private mixed $result;
- final public function __construct(?callable $executor = null)
+ public function __construct(?callable $executor = null)
{
if (\is_null($executor)) {
return;
diff --git a/src/Appwrite/Promises/Swoole.php b/src/Appwrite/Promises/Swoole.php
index 8cddd567f3..c258ef6a5e 100644
--- a/src/Appwrite/Promises/Swoole.php
+++ b/src/Appwrite/Promises/Swoole.php
@@ -6,16 +6,23 @@ use Swoole\Coroutine\Channel;
class Swoole extends Promise
{
+ public function __construct(?callable $executor = null)
+ {
+ parent::__construct($executor);
+ }
+
protected function execute(
callable $executor,
callable $resolve,
callable $reject
): void {
- try {
- $executor($resolve, $reject);
- } catch (\Throwable $exception) {
- $reject($exception);
- }
+ \go(function () use ($executor, $resolve, $reject) {
+ try {
+ $executor($resolve, $reject);
+ } catch (\Throwable $exception) {
+ $reject($exception);
+ }
+ });
}
/**
diff --git a/src/Appwrite/Specification/Format.php b/src/Appwrite/Specification/Format.php
index e2048971be..30ce6470e1 100644
--- a/src/Appwrite/Specification/Format.php
+++ b/src/Appwrite/Specification/Format.php
@@ -3,13 +3,13 @@
namespace Appwrite\Specification;
use Appwrite\Utopia\Response\Model;
+use Utopia\App;
use Utopia\Config\Config;
-use Utopia\Http\Http;
-use Utopia\Http\Route;
+use Utopia\Route;
abstract class Format
{
- protected Http $http;
+ protected App $app;
/**
* @var Route[]
@@ -50,9 +50,9 @@ abstract class Format
]
];
- public function __construct(Http $http, array $services, array $routes, array $models, array $keys, int $authCount)
+ public function __construct(App $app, array $services, array $routes, array $models, array $keys, int $authCount)
{
- $this->http = $http;
+ $this->app = $app;
$this->services = $services;
$this->routes = $routes;
$this->models = $models;
diff --git a/src/Appwrite/Specification/Format/OpenAPI3.php b/src/Appwrite/Specification/Format/OpenAPI3.php
index c9186ee0ac..f7430ec70e 100644
--- a/src/Appwrite/Specification/Format/OpenAPI3.php
+++ b/src/Appwrite/Specification/Format/OpenAPI3.php
@@ -7,11 +7,11 @@ use Appwrite\Template\Template;
use Appwrite\Utopia\Response\Model;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
-use Utopia\Http\Validator;
-use Utopia\Http\Validator\ArrayList;
-use Utopia\Http\Validator\Nullable;
-use Utopia\Http\Validator\Range;
-use Utopia\Http\Validator\WhiteList;
+use Utopia\Validator;
+use Utopia\Validator\ArrayList;
+use Utopia\Validator\Nullable;
+use Utopia\Validator\Range;
+use Utopia\Validator\WhiteList;
class OpenAPI3 extends Format
{
@@ -238,11 +238,8 @@ class OpenAPI3 extends Format
}
if ($route->getLabel('sdk.response.code', 500) === 204) {
- $labelCode = (string)$route->getLabel('sdk.response.code', '500');
- $temp['responses'][$labelCode]['description'] = 'No content';
- if (isset($temp['responses'][$labelCode]['schema'])) {
- unset($temp['responses'][$labelCode]['schema']);
- }
+ $temp['responses'][(string)$route->getLabel('sdk.response.code', '500')]['description'] = 'No content';
+ unset($temp['responses'][(string)$route->getLabel('sdk.response.code', '500')]['schema']);
}
if ((!empty($scope))) { // && 'public' != $scope
@@ -272,10 +269,10 @@ class OpenAPI3 extends Format
$bodyRequired = [];
foreach ($route->getParams() as $name => $param) { // Set params
- $injections = array_map(fn ($injection) => $this->http->getContainer()->get($injection), $param['injections'] ?? []);
-
- /** @var Validator $validator */
- $validator = (\is_callable($param['validator'])) ? call_user_func_array($param['validator'], $injections) : $param['validator'];
+ /**
+ * @var \Utopia\Validator $validator
+ */
+ $validator = (\is_callable($param['validator'])) ? call_user_func_array($param['validator'], $this->app->getResources($param['injections'])) : $param['validator'];
$node = [
'name' => $name,
@@ -292,11 +289,11 @@ class OpenAPI3 extends Format
switch ((!empty($validator)) ? \get_class($validator) : '') {
case 'Utopia\Database\Validator\UID':
- case 'Utopia\Http\Validator\Text':
+ case 'Utopia\Validator\Text':
$node['schema']['type'] = $validator->getType();
$node['schema']['x-example'] = '<' . \strtoupper(Template::fromCamelCaseToSnake($node['name'])) . '>';
break;
- case 'Utopia\Http\Validator\Boolean':
+ case 'Utopia\Validator\Boolean':
$node['schema']['type'] = $validator->getType();
$node['schema']['x-example'] = false;
break;
@@ -317,15 +314,15 @@ class OpenAPI3 extends Format
$node['schema']['format'] = 'email';
$node['schema']['x-example'] = 'email@example.com';
break;
- case 'Utopia\Http\Validator\Host':
- case 'Utopia\Http\Validator\URL':
+ case 'Utopia\Validator\Host':
+ case 'Utopia\Validator\URL':
$node['schema']['type'] = $validator->getType();
$node['schema']['format'] = 'url';
$node['schema']['x-example'] = 'https://example.com';
break;
- case 'Utopia\Http\Validator\JSON':
- case 'Utopia\Http\Validator\Mock':
- case 'Utopia\Http\Validator\Assoc':
+ case 'Utopia\Validator\JSON':
+ case 'Utopia\Validator\Mock':
+ case 'Utopia\Validator\Assoc':
$param['default'] = (empty($param['default'])) ? new \stdClass() : $param['default'];
$node['schema']['type'] = 'object';
$node['schema']['x-example'] = '{}';
@@ -335,7 +332,7 @@ class OpenAPI3 extends Format
$node['schema']['type'] = $validator->getType();
$node['schema']['format'] = 'binary';
break;
- case 'Utopia\Http\Validator\ArrayList':
+ case 'Utopia\Validator\ArrayList':
/** @var ArrayList $validator */
$node['schema']['type'] = 'array';
$node['schema']['items'] = [
@@ -397,25 +394,25 @@ class OpenAPI3 extends Format
$node['schema']['format'] = 'phone';
$node['schema']['x-example'] = '+12065550100'; // In the US, 555 is reserved like example.com
break;
- case 'Utopia\Http\Validator\Range':
+ case 'Utopia\Validator\Range':
/** @var Range $validator */
$node['schema']['type'] = $validator->getType() === Validator::TYPE_FLOAT ? 'number' : $validator->getType();
$node['schema']['format'] = $validator->getType() == Validator::TYPE_INTEGER ? 'int32' : 'float';
$node['schema']['x-example'] = $validator->getMin();
break;
- case 'Utopia\Http\Validator\Numeric':
- case 'Utopia\Http\Validator\Integer':
+ case 'Utopia\Validator\Numeric':
+ case 'Utopia\Validator\Integer':
$node['schema']['type'] = $validator->getType();
$node['schema']['format'] = 'int32';
break;
- case 'Utopia\Http\Validator\FloatValidator':
+ case 'Utopia\Validator\FloatValidator':
$node['schema']['type'] = 'number';
$node['schema']['format'] = 'float';
break;
- case 'Utopia\Http\Validator\Length':
+ case 'Utopia\Validator\Length':
$node['schema']['type'] = $validator->getType();
break;
- case 'Utopia\Http\Validator\WhiteList':
+ case 'Utopia\Validator\WhiteList':
/** @var WhiteList $validator */
$node['schema']['type'] = $validator->getType();
$node['schema']['x-example'] = $validator->getList()[0];
@@ -552,6 +549,7 @@ class OpenAPI3 extends Format
switch ($rule['type']) {
case 'string':
case 'datetime':
+ case 'payload':
$type = 'string';
break;
diff --git a/src/Appwrite/Specification/Format/Swagger2.php b/src/Appwrite/Specification/Format/Swagger2.php
index 81e0f2c41d..f2e324c71f 100644
--- a/src/Appwrite/Specification/Format/Swagger2.php
+++ b/src/Appwrite/Specification/Format/Swagger2.php
@@ -7,10 +7,10 @@ use Appwrite\Template\Template;
use Appwrite\Utopia\Response\Model;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
-use Utopia\Http\Validator;
-use Utopia\Http\Validator\ArrayList;
-use Utopia\Http\Validator\Nullable;
-use Utopia\Http\Validator\Range;
+use Utopia\Validator;
+use Utopia\Validator\ArrayList;
+use Utopia\Validator\Nullable;
+use Utopia\Validator\Range;
class Swagger2 extends Format
{
@@ -270,10 +270,8 @@ class Swagger2 extends Format
);
foreach ($parameters as $name => $param) { // Set params
- $injections = array_map(fn ($injection) => $this->http->getContainer()->get($injection), $param['injections'] ?? []);
-
/** @var Validator $validator */
- $validator = (\is_callable($param['validator'])) ? call_user_func_array($param['validator'], $injections) : $param['validator'];
+ $validator = (\is_callable($param['validator'])) ? call_user_func_array($param['validator'], $this->app->getResources($param['injections'])) : $param['validator'];
$node = [
'name' => $name,
@@ -288,19 +286,32 @@ class Swagger2 extends Format
$validator = $validator->getValidator();
}
- $validatorClass = (!empty($validator)) ? \get_class($validator) : '';
- if ($validatorClass === 'Utopia\Http\Validator\AnyOf') {
- $validator = $param['validator']->getValidators()[0];
- $validatorClass = \get_class($validator);
+ $class = !empty($validator)
+ ? \get_class($validator)
+ : '';
+
+ $base = !empty($class)
+ ? \get_parent_class($class)
+ : '';
+
+ switch ($base) {
+ case 'Appwrite\Utopia\Database\Validator\Queries\Base':
+ $class = $base;
+ break;
}
- switch ($validatorClass) {
- case 'Utopia\Http\Validator\Text':
+ if ($class === 'Utopia\Validator\AnyOf') {
+ $validator = $param['validator']->getValidators()[0];
+ $class = \get_class($validator);
+ }
+
+ switch ($class) {
+ case 'Utopia\Validator\Text':
case 'Utopia\Database\Validator\UID':
$node['type'] = $validator->getType();
$node['x-example'] = '<' . \strtoupper(Template::fromCamelCaseToSnake($node['name'])) . '>';
break;
- case 'Utopia\Http\Validator\Boolean':
+ case 'Utopia\Validator\Boolean':
$node['type'] = $validator->getType();
$node['x-example'] = false;
break;
@@ -321,13 +332,13 @@ class Swagger2 extends Format
$node['format'] = 'email';
$node['x-example'] = 'email@example.com';
break;
- case 'Utopia\Http\Validator\Host':
- case 'Utopia\Http\Validator\URL':
+ case 'Utopia\Validator\Host':
+ case 'Utopia\Validator\URL':
$node['type'] = $validator->getType();
$node['format'] = 'url';
$node['x-example'] = 'https://example.com';
break;
- case 'Utopia\Http\Validator\ArrayList':
+ case 'Utopia\Validator\ArrayList':
/** @var ArrayList $validator */
$node['type'] = 'array';
$node['collectionFormat'] = 'multi';
@@ -335,9 +346,9 @@ class Swagger2 extends Format
'type' => $validator->getValidator()->getType(),
];
break;
- case 'Utopia\Http\Validator\JSON':
- case 'Utopia\Http\Validator\Mock':
- case 'Utopia\Http\Validator\Assoc':
+ case 'Utopia\Validator\JSON':
+ case 'Utopia\Validator\Mock':
+ case 'Utopia\Validator\Assoc':
$node['type'] = 'object';
$node['default'] = (empty($param['default'])) ? new \stdClass() : $param['default'];
$node['x-example'] = '{}';
@@ -350,29 +361,7 @@ class Swagger2 extends Format
$consumes = ['multipart/form-data'];
$node['type'] = 'payload';
break;
- case 'Appwrite\Utopia\Database\Validator\Queries\Attributes':
- case 'Appwrite\Utopia\Database\Validator\Queries\Buckets':
- case 'Appwrite\Utopia\Database\Validator\Queries\Collections':
- case 'Appwrite\Utopia\Database\Validator\Queries\Databases':
- case 'Appwrite\Utopia\Database\Validator\Queries\Deployments':
- case 'Appwrite\Utopia\Database\Validator\Queries\Executions':
- case 'Appwrite\Utopia\Database\Validator\Queries\Files':
- case 'Appwrite\Utopia\Database\Validator\Queries\Functions':
- case 'Appwrite\Utopia\Database\Validator\Queries\Identities':
- case 'Appwrite\Utopia\Database\Validator\Queries\Indexes':
- case 'Appwrite\Utopia\Database\Validator\Queries\Installations':
- case 'Appwrite\Utopia\Database\Validator\Queries\Memberships':
- case 'Appwrite\Utopia\Database\Validator\Queries\Messages':
- case 'Appwrite\Utopia\Database\Validator\Queries\Migrations':
- case 'Appwrite\Utopia\Database\Validator\Queries\Projects':
- case 'Appwrite\Utopia\Database\Validator\Queries\Providers':
- case 'Appwrite\Utopia\Database\Validator\Queries\Rules':
- case 'Appwrite\Utopia\Database\Validator\Queries\Subscribers':
- case 'Appwrite\Utopia\Database\Validator\Queries\Targets':
- case 'Appwrite\Utopia\Database\Validator\Queries\Teams':
- case 'Appwrite\Utopia\Database\Validator\Queries\Topics':
- case 'Appwrite\Utopia\Database\Validator\Queries\Users':
- case 'Appwrite\Utopia\Database\Validator\Queries\Variables':
+ case 'Appwrite\Utopia\Database\Validator\Queries\Base':
case 'Utopia\Database\Validator\Queries':
case 'Utopia\Database\Validator\Queries\Document':
case 'Utopia\Database\Validator\Queries\Documents':
@@ -408,26 +397,26 @@ class Swagger2 extends Format
$node['format'] = 'phone';
$node['x-example'] = '+12065550100';
break;
- case 'Utopia\Http\Validator\Range':
+ case 'Utopia\Validator\Range':
/** @var Range $validator */
$node['type'] = $validator->getType() === Validator::TYPE_FLOAT ? 'number' : $validator->getType();
$node['format'] = $validator->getType() == Validator::TYPE_INTEGER ? 'int32' : 'float';
$node['x-example'] = $validator->getMin();
break;
- case 'Utopia\Http\Validator\Numeric':
- case 'Utopia\Http\Validator\Integer':
+ case 'Utopia\Validator\Numeric':
+ case 'Utopia\Validator\Integer':
$node['type'] = $validator->getType();
$node['format'] = 'int32';
break;
- case 'Utopia\Http\Validator\FloatValidator':
+ case 'Utopia\Validator\FloatValidator':
$node['type'] = 'number';
$node['format'] = 'float';
break;
- case 'Utopia\Http\Validator\Length':
+ case 'Utopia\Validator\Length':
$node['type'] = $validator->getType();
break;
- case 'Utopia\Http\Validator\WhiteList':
- /** @var \Utopia\Http\Validator\WhiteList $validator */
+ case 'Utopia\Validator\WhiteList':
+ /** @var \Utopia\Validator\WhiteList $validator */
$node['type'] = $validator->getType();
$node['x-example'] = $validator->getList()[0];
@@ -587,6 +576,10 @@ class Swagger2 extends Format
$type = 'boolean';
break;
+ case 'payload':
+ $type = 'payload';
+ break;
+
default:
$type = 'object';
$rule['type'] = ($rule['type']) ?: 'none';
diff --git a/src/Appwrite/Task/Validator/Cron.php b/src/Appwrite/Task/Validator/Cron.php
index afa19c50a6..03bd1c5220 100644
--- a/src/Appwrite/Task/Validator/Cron.php
+++ b/src/Appwrite/Task/Validator/Cron.php
@@ -3,7 +3,7 @@
namespace Appwrite\Task\Validator;
use Cron\CronExpression;
-use Utopia\Http\Validator;
+use Utopia\Validator;
class Cron extends Validator
{
diff --git a/src/Appwrite/Utopia/Database/Validator/CompoundUID.php b/src/Appwrite/Utopia/Database/Validator/CompoundUID.php
index b851d8ba85..3f23500952 100644
--- a/src/Appwrite/Utopia/Database/Validator/CompoundUID.php
+++ b/src/Appwrite/Utopia/Database/Validator/CompoundUID.php
@@ -3,7 +3,7 @@
namespace Appwrite\Utopia\Database\Validator;
use Utopia\Database\Validator\UID;
-use Utopia\Http\Validator;
+use Utopia\Validator;
class CompoundUID extends Validator
{
diff --git a/src/Appwrite/Utopia/Database/Validator/ProjectId.php b/src/Appwrite/Utopia/Database/Validator/ProjectId.php
index 28abe176fe..46b0cdf53e 100644
--- a/src/Appwrite/Utopia/Database/Validator/ProjectId.php
+++ b/src/Appwrite/Utopia/Database/Validator/ProjectId.php
@@ -2,7 +2,7 @@
namespace Appwrite\Utopia\Database\Validator;
-use Utopia\Http\Validator;
+use Utopia\Validator;
class ProjectId extends Validator
{
diff --git a/src/Appwrite/Utopia/Queue/Connections.php b/src/Appwrite/Utopia/Queue/Connections.php
deleted file mode 100644
index e873373566..0000000000
--- a/src/Appwrite/Utopia/Queue/Connections.php
+++ /dev/null
@@ -1,36 +0,0 @@
-connections[] = ['connection' => $connection, 'pool' => $pool];
- return $this;
- }
-
- /**
- * @return self
- */
- public function reclaim(): self
- {
- foreach ($this->connections as $id => $resource) {
- $pool = $resource['pool'];
- $connection = $resource['connection'];
- $pool->put($connection);
- unset($this->connections[$id]);
- }
-
- return $this;
- }
-}
diff --git a/src/Appwrite/Utopia/Request.php b/src/Appwrite/Utopia/Request.php
index 6be9701baa..26c1baf188 100644
--- a/src/Appwrite/Utopia/Request.php
+++ b/src/Appwrite/Utopia/Request.php
@@ -3,10 +3,11 @@
namespace Appwrite\Utopia;
use Appwrite\Utopia\Request\Filter;
-use Utopia\Http\Adapter\Swoole\Request as HttpRequest;
-use Utopia\Http\Route;
+use Swoole\Http\Request as SwooleRequest;
+use Utopia\Route;
+use Utopia\Swoole\Request as UtopiaRequest;
-class Request extends HttpRequest
+class Request extends UtopiaRequest
{
/**
* @var array
@@ -14,12 +15,9 @@ class Request extends HttpRequest
private array $filters = [];
private static ?Route $route = null;
- /**
- * Request constructor.
- */
- public function __construct(HttpRequest $request)
+ public function __construct(SwooleRequest $request)
{
- parent::__construct($request->swoole);
+ parent::__construct($request);
}
/**
@@ -115,16 +113,6 @@ class Request extends HttpRequest
return self::$route !== null;
}
-
- public function removeHeader(string $key): static
- {
- if (isset($this->headers[$key])) {
- unset($this->headers[$key]);
- }
-
- return parent::removeHeader($key);
- }
-
/**
* Get headers
*
@@ -134,14 +122,14 @@ class Request extends HttpRequest
*/
public function getHeaders(): array
{
- if ($this->headers !== null) {
- return $this->headers;
+ try {
+ $headers = $this->generateHeaders();
+ } catch (\Throwable) {
+ $headers = [];
}
- $this->headers = $this->generateHeaders();
-
if (empty($this->swoole->cookie)) {
- return $this->headers;
+ return $headers;
}
$cookieHeaders = [];
@@ -150,10 +138,10 @@ class Request extends HttpRequest
}
if (!empty($cookieHeaders)) {
- $this->headers['cookie'] = \implode('; ', $cookieHeaders);
+ $headers['cookie'] = \implode('; ', $cookieHeaders);
}
- return $this->headers;
+ return $headers;
}
/**
diff --git a/src/Appwrite/Utopia/Response.php b/src/Appwrite/Utopia/Response.php
index 3808b1e9f8..6cc2639f51 100644
--- a/src/Appwrite/Utopia/Response.php
+++ b/src/Appwrite/Utopia/Response.php
@@ -4,20 +4,122 @@ namespace Appwrite\Utopia;
use Appwrite\Utopia\Fetch\BodyMultipart;
use Appwrite\Utopia\Response\Filter;
-use Appwrite\Utopia\Response\Models;
+use Appwrite\Utopia\Response\Model;
+use Appwrite\Utopia\Response\Model\Account;
+use Appwrite\Utopia\Response\Model\AlgoArgon2;
+use Appwrite\Utopia\Response\Model\AlgoBcrypt;
+use Appwrite\Utopia\Response\Model\AlgoMd5;
+use Appwrite\Utopia\Response\Model\AlgoPhpass;
+use Appwrite\Utopia\Response\Model\AlgoScrypt;
+use Appwrite\Utopia\Response\Model\AlgoScryptModified;
+use Appwrite\Utopia\Response\Model\AlgoSha;
+use Appwrite\Utopia\Response\Model\Any;
+use Appwrite\Utopia\Response\Model\Attribute;
+use Appwrite\Utopia\Response\Model\AttributeBoolean;
+use Appwrite\Utopia\Response\Model\AttributeDatetime;
+use Appwrite\Utopia\Response\Model\AttributeEmail;
+use Appwrite\Utopia\Response\Model\AttributeEnum;
+use Appwrite\Utopia\Response\Model\AttributeFloat;
+use Appwrite\Utopia\Response\Model\AttributeInteger;
+use Appwrite\Utopia\Response\Model\AttributeIP;
+use Appwrite\Utopia\Response\Model\AttributeList;
+use Appwrite\Utopia\Response\Model\AttributeRelationship;
+use Appwrite\Utopia\Response\Model\AttributeString;
+use Appwrite\Utopia\Response\Model\AttributeURL;
+use Appwrite\Utopia\Response\Model\AuthProvider;
+use Appwrite\Utopia\Response\Model\BaseList;
+use Appwrite\Utopia\Response\Model\Branch;
+use Appwrite\Utopia\Response\Model\Bucket;
+use Appwrite\Utopia\Response\Model\Build;
+use Appwrite\Utopia\Response\Model\Collection;
+use Appwrite\Utopia\Response\Model\ConsoleVariables;
+use Appwrite\Utopia\Response\Model\Continent;
+use Appwrite\Utopia\Response\Model\Country;
+use Appwrite\Utopia\Response\Model\Currency;
+use Appwrite\Utopia\Response\Model\Database;
+use Appwrite\Utopia\Response\Model\Deployment;
+use Appwrite\Utopia\Response\Model\Detection;
+use Appwrite\Utopia\Response\Model\Document as ModelDocument;
+use Appwrite\Utopia\Response\Model\Error;
+use Appwrite\Utopia\Response\Model\ErrorDev;
+use Appwrite\Utopia\Response\Model\Execution;
+use Appwrite\Utopia\Response\Model\File;
+use Appwrite\Utopia\Response\Model\Func;
+use Appwrite\Utopia\Response\Model\Headers;
+use Appwrite\Utopia\Response\Model\HealthAntivirus;
+use Appwrite\Utopia\Response\Model\HealthCertificate;
+use Appwrite\Utopia\Response\Model\HealthQueue;
+use Appwrite\Utopia\Response\Model\HealthStatus;
+use Appwrite\Utopia\Response\Model\HealthTime;
+use Appwrite\Utopia\Response\Model\HealthVersion;
+use Appwrite\Utopia\Response\Model\Identity;
+use Appwrite\Utopia\Response\Model\Index;
+use Appwrite\Utopia\Response\Model\Installation;
+use Appwrite\Utopia\Response\Model\JWT;
+use Appwrite\Utopia\Response\Model\Key;
+use Appwrite\Utopia\Response\Model\Language;
+use Appwrite\Utopia\Response\Model\Locale;
+use Appwrite\Utopia\Response\Model\LocaleCode;
+use Appwrite\Utopia\Response\Model\Log;
+use Appwrite\Utopia\Response\Model\Membership;
+use Appwrite\Utopia\Response\Model\Message;
+use Appwrite\Utopia\Response\Model\Metric;
+use Appwrite\Utopia\Response\Model\MetricBreakdown;
+use Appwrite\Utopia\Response\Model\MFAChallenge;
+use Appwrite\Utopia\Response\Model\MFAFactors;
+use Appwrite\Utopia\Response\Model\MFARecoveryCodes;
+use Appwrite\Utopia\Response\Model\MFAType;
+use Appwrite\Utopia\Response\Model\Migration;
+use Appwrite\Utopia\Response\Model\MigrationFirebaseProject;
+use Appwrite\Utopia\Response\Model\MigrationReport;
+use Appwrite\Utopia\Response\Model\Mock;
+use Appwrite\Utopia\Response\Model\MockNumber;
+use Appwrite\Utopia\Response\Model\None;
+use Appwrite\Utopia\Response\Model\Phone;
+use Appwrite\Utopia\Response\Model\Platform;
+use Appwrite\Utopia\Response\Model\Preferences;
+use Appwrite\Utopia\Response\Model\Project;
+use Appwrite\Utopia\Response\Model\Provider;
+use Appwrite\Utopia\Response\Model\ProviderRepository;
+use Appwrite\Utopia\Response\Model\Rule;
+use Appwrite\Utopia\Response\Model\Runtime;
+use Appwrite\Utopia\Response\Model\Session;
+use Appwrite\Utopia\Response\Model\Specification;
+use Appwrite\Utopia\Response\Model\Subscriber;
+use Appwrite\Utopia\Response\Model\Target;
+use Appwrite\Utopia\Response\Model\Team;
+use Appwrite\Utopia\Response\Model\TemplateEmail;
+use Appwrite\Utopia\Response\Model\TemplateFunction;
+use Appwrite\Utopia\Response\Model\TemplateRuntime;
+use Appwrite\Utopia\Response\Model\TemplateSMS;
+use Appwrite\Utopia\Response\Model\TemplateVariable;
+use Appwrite\Utopia\Response\Model\Token;
+use Appwrite\Utopia\Response\Model\Topic;
+use Appwrite\Utopia\Response\Model\UsageBuckets;
+use Appwrite\Utopia\Response\Model\UsageCollection;
+use Appwrite\Utopia\Response\Model\UsageDatabase;
+use Appwrite\Utopia\Response\Model\UsageDatabases;
+use Appwrite\Utopia\Response\Model\UsageFunction;
+use Appwrite\Utopia\Response\Model\UsageFunctions;
+use Appwrite\Utopia\Response\Model\UsageProject;
+use Appwrite\Utopia\Response\Model\UsageStorage;
+use Appwrite\Utopia\Response\Model\UsageUsers;
+use Appwrite\Utopia\Response\Model\User;
+use Appwrite\Utopia\Response\Model\Variable;
+use Appwrite\Utopia\Response\Model\VcsContent;
+use Appwrite\Utopia\Response\Model\Webhook;
use Exception;
use JsonException;
+use Swoole\Http\Response as SwooleHTTPResponse;
// Keep last
use Utopia\Database\Document;
-use Utopia\Http\Adapter\Swoole\Response as HttpResponse;
-
-// Keep last
+use Utopia\Swoole\Response as SwooleResponse;
/**
* @method int getStatusCode()
* @method Response setStatusCode(int $code = 200)
*/
-class Response extends HttpResponse
+class Response extends SwooleResponse
{
// General
public const MODEL_NONE = 'none';
@@ -228,9 +330,162 @@ class Response extends HttpResponse
*
* @param float $time
*/
- public function __construct(HttpResponse $response)
+ public function __construct(SwooleHTTPResponse $response)
{
- parent::__construct($response->swoole);
+ $this
+ // General
+ ->setModel(new None())
+ ->setModel(new Any())
+ ->setModel(new Error())
+ ->setModel(new ErrorDev())
+ // Lists
+ ->setModel(new BaseList('Documents List', self::MODEL_DOCUMENT_LIST, 'documents', self::MODEL_DOCUMENT))
+ ->setModel(new BaseList('Collections List', self::MODEL_COLLECTION_LIST, 'collections', self::MODEL_COLLECTION))
+ ->setModel(new BaseList('Databases List', self::MODEL_DATABASE_LIST, 'databases', self::MODEL_DATABASE))
+ ->setModel(new BaseList('Indexes List', self::MODEL_INDEX_LIST, 'indexes', self::MODEL_INDEX))
+ ->setModel(new BaseList('Users List', self::MODEL_USER_LIST, 'users', self::MODEL_USER))
+ ->setModel(new BaseList('Sessions List', self::MODEL_SESSION_LIST, 'sessions', self::MODEL_SESSION))
+ ->setModel(new BaseList('Identities List', self::MODEL_IDENTITY_LIST, 'identities', self::MODEL_IDENTITY))
+ ->setModel(new BaseList('Logs List', self::MODEL_LOG_LIST, 'logs', self::MODEL_LOG))
+ ->setModel(new BaseList('Files List', self::MODEL_FILE_LIST, 'files', self::MODEL_FILE))
+ ->setModel(new BaseList('Buckets List', self::MODEL_BUCKET_LIST, 'buckets', self::MODEL_BUCKET))
+ ->setModel(new BaseList('Teams List', self::MODEL_TEAM_LIST, 'teams', self::MODEL_TEAM))
+ ->setModel(new BaseList('Memberships List', self::MODEL_MEMBERSHIP_LIST, 'memberships', self::MODEL_MEMBERSHIP))
+ ->setModel(new BaseList('Functions List', self::MODEL_FUNCTION_LIST, 'functions', self::MODEL_FUNCTION))
+ ->setModel(new BaseList('Function Templates List', self::MODEL_TEMPLATE_FUNCTION_LIST, 'templates', self::MODEL_TEMPLATE_FUNCTION))
+ ->setModel(new BaseList('Installations List', self::MODEL_INSTALLATION_LIST, 'installations', self::MODEL_INSTALLATION))
+ ->setModel(new BaseList('Provider Repositories List', self::MODEL_PROVIDER_REPOSITORY_LIST, 'providerRepositories', self::MODEL_PROVIDER_REPOSITORY))
+ ->setModel(new BaseList('Branches List', self::MODEL_BRANCH_LIST, 'branches', self::MODEL_BRANCH))
+ ->setModel(new BaseList('Runtimes List', self::MODEL_RUNTIME_LIST, 'runtimes', self::MODEL_RUNTIME))
+ ->setModel(new BaseList('Deployments List', self::MODEL_DEPLOYMENT_LIST, 'deployments', self::MODEL_DEPLOYMENT))
+ ->setModel(new BaseList('Executions List', self::MODEL_EXECUTION_LIST, 'executions', self::MODEL_EXECUTION))
+ ->setModel(new BaseList('Builds List', self::MODEL_BUILD_LIST, 'builds', self::MODEL_BUILD)) // Not used anywhere yet
+ ->setModel(new BaseList('Projects List', self::MODEL_PROJECT_LIST, 'projects', self::MODEL_PROJECT, true, false))
+ ->setModel(new BaseList('Webhooks List', self::MODEL_WEBHOOK_LIST, 'webhooks', self::MODEL_WEBHOOK, true, false))
+ ->setModel(new BaseList('API Keys List', self::MODEL_KEY_LIST, 'keys', self::MODEL_KEY, true, false))
+ ->setModel(new BaseList('Auth Providers List', self::MODEL_AUTH_PROVIDER_LIST, 'platforms', self::MODEL_AUTH_PROVIDER, true, false))
+ ->setModel(new BaseList('Platforms List', self::MODEL_PLATFORM_LIST, 'platforms', self::MODEL_PLATFORM, true, false))
+ ->setModel(new BaseList('Countries List', self::MODEL_COUNTRY_LIST, 'countries', self::MODEL_COUNTRY))
+ ->setModel(new BaseList('Continents List', self::MODEL_CONTINENT_LIST, 'continents', self::MODEL_CONTINENT))
+ ->setModel(new BaseList('Languages List', self::MODEL_LANGUAGE_LIST, 'languages', self::MODEL_LANGUAGE))
+ ->setModel(new BaseList('Currencies List', self::MODEL_CURRENCY_LIST, 'currencies', self::MODEL_CURRENCY))
+ ->setModel(new BaseList('Phones List', self::MODEL_PHONE_LIST, 'phones', self::MODEL_PHONE))
+ ->setModel(new BaseList('Metric List', self::MODEL_METRIC_LIST, 'metrics', self::MODEL_METRIC, true, false))
+ ->setModel(new BaseList('Variables List', self::MODEL_VARIABLE_LIST, 'variables', self::MODEL_VARIABLE))
+ ->setModel(new BaseList('Status List', self::MODEL_HEALTH_STATUS_LIST, 'statuses', self::MODEL_HEALTH_STATUS))
+ ->setModel(new BaseList('Rule List', self::MODEL_PROXY_RULE_LIST, 'rules', self::MODEL_PROXY_RULE))
+ ->setModel(new BaseList('Locale codes list', self::MODEL_LOCALE_CODE_LIST, 'localeCodes', self::MODEL_LOCALE_CODE))
+ ->setModel(new BaseList('Provider list', self::MODEL_PROVIDER_LIST, 'providers', self::MODEL_PROVIDER))
+ ->setModel(new BaseList('Message list', self::MODEL_MESSAGE_LIST, 'messages', self::MODEL_MESSAGE))
+ ->setModel(new BaseList('Topic list', self::MODEL_TOPIC_LIST, 'topics', self::MODEL_TOPIC))
+ ->setModel(new BaseList('Subscriber list', self::MODEL_SUBSCRIBER_LIST, 'subscribers', self::MODEL_SUBSCRIBER))
+ ->setModel(new BaseList('Target list', self::MODEL_TARGET_LIST, 'targets', self::MODEL_TARGET))
+ ->setModel(new BaseList('Migrations List', self::MODEL_MIGRATION_LIST, 'migrations', self::MODEL_MIGRATION))
+ ->setModel(new BaseList('Migrations Firebase Projects List', self::MODEL_MIGRATION_FIREBASE_PROJECT_LIST, 'projects', self::MODEL_MIGRATION_FIREBASE_PROJECT))
+ ->setModel(new BaseList('Specifications List', self::MODEL_SPECIFICATION_LIST, 'specifications', self::MODEL_SPECIFICATION))
+ ->setModel(new BaseList('VCS Content List', self::MODEL_VCS_CONTENT_LIST, 'contents', self::MODEL_VCS_CONTENT))
+ // Entities
+ ->setModel(new Database())
+ ->setModel(new Collection())
+ ->setModel(new Attribute())
+ ->setModel(new AttributeList())
+ ->setModel(new AttributeString())
+ ->setModel(new AttributeInteger())
+ ->setModel(new AttributeFloat())
+ ->setModel(new AttributeBoolean())
+ ->setModel(new AttributeEmail())
+ ->setModel(new AttributeEnum())
+ ->setModel(new AttributeIP())
+ ->setModel(new AttributeURL())
+ ->setModel(new AttributeDatetime())
+ ->setModel(new AttributeRelationship())
+ ->setModel(new Index())
+ ->setModel(new ModelDocument())
+ ->setModel(new Log())
+ ->setModel(new User())
+ ->setModel(new AlgoMd5())
+ ->setModel(new AlgoSha())
+ ->setModel(new AlgoPhpass())
+ ->setModel(new AlgoBcrypt())
+ ->setModel(new AlgoScrypt())
+ ->setModel(new AlgoScryptModified())
+ ->setModel(new AlgoArgon2())
+ ->setModel(new Account())
+ ->setModel(new Preferences())
+ ->setModel(new Session())
+ ->setModel(new Identity())
+ ->setModel(new Token())
+ ->setModel(new JWT())
+ ->setModel(new Locale())
+ ->setModel(new LocaleCode())
+ ->setModel(new File())
+ ->setModel(new Bucket())
+ ->setModel(new Team())
+ ->setModel(new Membership())
+ ->setModel(new Func())
+ ->setModel(new TemplateFunction())
+ ->setModel(new TemplateRuntime())
+ ->setModel(new TemplateVariable())
+ ->setModel(new Installation())
+ ->setModel(new ProviderRepository())
+ ->setModel(new Detection())
+ ->setModel(new VcsContent())
+ ->setModel(new Branch())
+ ->setModel(new Runtime())
+ ->setModel(new Deployment())
+ ->setModel(new Execution())
+ ->setModel(new Build())
+ ->setModel(new Project())
+ ->setModel(new Webhook())
+ ->setModel(new Key())
+ ->setModel(new MockNumber())
+ ->setModel(new AuthProvider())
+ ->setModel(new Platform())
+ ->setModel(new Variable())
+ ->setModel(new Country())
+ ->setModel(new Continent())
+ ->setModel(new Language())
+ ->setModel(new Currency())
+ ->setModel(new Phone())
+ ->setModel(new HealthAntivirus())
+ ->setModel(new HealthQueue())
+ ->setModel(new HealthStatus())
+ ->setModel(new HealthCertificate())
+ ->setModel(new HealthTime())
+ ->setModel(new HealthVersion())
+ ->setModel(new Metric())
+ ->setModel(new MetricBreakdown())
+ ->setModel(new UsageDatabases())
+ ->setModel(new UsageDatabase())
+ ->setModel(new UsageCollection())
+ ->setModel(new UsageUsers())
+ ->setModel(new UsageStorage())
+ ->setModel(new UsageBuckets())
+ ->setModel(new UsageFunctions())
+ ->setModel(new UsageFunction())
+ ->setModel(new UsageProject())
+ ->setModel(new Headers())
+ ->setModel(new Specification())
+ ->setModel(new Rule())
+ ->setModel(new TemplateSMS())
+ ->setModel(new TemplateEmail())
+ ->setModel(new ConsoleVariables())
+ ->setModel(new MFAChallenge())
+ ->setModel(new MFARecoveryCodes())
+ ->setModel(new MFAType())
+ ->setModel(new MFAFactors())
+ ->setModel(new Provider())
+ ->setModel(new Message())
+ ->setModel(new Topic())
+ ->setModel(new Subscriber())
+ ->setModel(new Target())
+ ->setModel(new Migration())
+ ->setModel(new MigrationReport())
+ ->setModel(new MigrationFirebaseProject())
+ // Tests (keep last)
+ ->setModel(new Mock());
+
+ parent::__construct($response);
}
/**
@@ -240,6 +495,49 @@ class Response extends HttpResponse
public const CONTENT_TYPE_NULL = 'null';
public const CONTENT_TYPE_MULTIPART = 'multipart/form-data';
+ /**
+ * List of defined output objects
+ */
+ protected $models = [];
+
+ /**
+ * Set Model Object
+ *
+ * @return self
+ */
+ public function setModel(Model $instance)
+ {
+ $this->models[$instance->getType()] = $instance;
+
+ return $this;
+ }
+
+ /**
+ * Get Model Object
+ *
+ * @param string $key
+ * @return Model
+ * @throws Exception
+ */
+ public function getModel(string $key): Model
+ {
+ if (!isset($this->models[$key])) {
+ throw new Exception('Undefined model: ' . $key);
+ }
+
+ return $this->models[$key];
+ }
+
+ /**
+ * Get Models List
+ *
+ * @return Model[]
+ */
+ public function getModels(): array
+ {
+ return $this->models;
+ }
+
public function applyFilters(array $data, string $model): array
{
foreach ($this->filters as $filter) {
@@ -307,7 +605,7 @@ class Response extends HttpResponse
public function output(Document $document, string $model): array
{
$data = clone $document;
- $model = Models::getModel($model);
+ $model = $this->getModel($model);
$output = [];
$data = $model->filter($data);
@@ -327,6 +625,11 @@ class Response extends HttpResponse
}
}
+ if (!$data->isSet($key) && !$rule['required']) { // set output key null if data key is not set and required is false
+ $output[$key] = null;
+ continue;
+ }
+
if ($rule['array']) {
if (!is_array($data[$key])) {
throw new Exception($key . ' must be an array of type ' . $rule['type']);
@@ -337,7 +640,7 @@ class Response extends HttpResponse
if (\is_array($rule['type'])) {
foreach ($rule['type'] as $type) {
$condition = false;
- foreach (Models::getModel($type)->conditions as $attribute => $val) {
+ foreach ($this->getModel($type)->conditions as $attribute => $val) {
$condition = $item->getAttribute($attribute) === $val;
if (!$condition) {
break;
@@ -352,7 +655,7 @@ class Response extends HttpResponse
$ruleType = $rule['type'];
}
- if (!array_key_exists($ruleType, Models::getModels())) {
+ if (!array_key_exists($ruleType, $this->models)) {
throw new Exception('Missing model for rule: ' . $ruleType);
}
diff --git a/src/Appwrite/Utopia/Response/Model.php b/src/Appwrite/Utopia/Response/Model.php
index 8a0bb78cba..d14d1be0c1 100644
--- a/src/Appwrite/Utopia/Response/Model.php
+++ b/src/Appwrite/Utopia/Response/Model.php
@@ -14,6 +14,7 @@ abstract class Model
public const TYPE_DATETIME = 'datetime';
public const TYPE_DATETIME_EXAMPLE = '2020-10-15T06:38:00.000+00:00';
public const TYPE_RELATIONSHIP = 'relationship';
+ public const TYPE_PAYLOAD = 'payload';
/**
* @var bool
diff --git a/src/Appwrite/Utopia/Response/Model/Attribute.php b/src/Appwrite/Utopia/Response/Model/Attribute.php
index 9f9ceca317..8c43f8d21c 100644
--- a/src/Appwrite/Utopia/Response/Model/Attribute.php
+++ b/src/Appwrite/Utopia/Response/Model/Attribute.php
@@ -47,7 +47,18 @@ class Attribute extends Model
'required' => false,
'example' => false,
])
- ;
+ ->addRule('$createdAt', [
+ 'type' => self::TYPE_DATETIME,
+ 'description' => 'Attribute creation date in ISO 8601 format.',
+ 'default' => '',
+ 'example' => self::TYPE_DATETIME_EXAMPLE,
+ ])
+ ->addRule('$updatedAt', [
+ 'type' => self::TYPE_DATETIME,
+ 'description' => 'Attribute update date in ISO 8601 format.',
+ 'default' => '',
+ 'example' => self::TYPE_DATETIME_EXAMPLE,
+ ]);
}
public array $conditions = [];
diff --git a/src/Appwrite/Utopia/Response/Model/Execution.php b/src/Appwrite/Utopia/Response/Model/Execution.php
index 90fbdc9689..dc5d41c02c 100644
--- a/src/Appwrite/Utopia/Response/Model/Execution.php
+++ b/src/Appwrite/Utopia/Response/Model/Execution.php
@@ -81,10 +81,9 @@ class Execution extends Model
'example' => 200,
])
->addRule('responseBody', [
- 'type' => self::TYPE_STRING,
+ 'type' => self::TYPE_PAYLOAD,
'description' => 'HTTP response body. This will return empty unless execution is created as synchronous.',
'default' => '',
- 'example' => 'Developers are awesome.',
])
->addRule('responseHeaders', [
'type' => Response::MODEL_HEADERS,
diff --git a/src/Appwrite/Utopia/Response/Model/Index.php b/src/Appwrite/Utopia/Response/Model/Index.php
index 3d3d1a3b52..2d795ad439 100644
--- a/src/Appwrite/Utopia/Response/Model/Index.php
+++ b/src/Appwrite/Utopia/Response/Model/Index.php
@@ -49,13 +49,22 @@ class Index extends Model
'array' => true,
'required' => false,
])
- ;
+ ->addRule('$createdAt', [
+ 'type' => self::TYPE_DATETIME,
+ 'description' => 'Index creation date in ISO 8601 format.',
+ 'default' => '',
+ 'example' => self::TYPE_DATETIME_EXAMPLE,
+ ])
+ ->addRule('$updatedAt', [
+ 'type' => self::TYPE_DATETIME,
+ 'description' => 'Index update date in ISO 8601 format.',
+ 'default' => '',
+ 'example' => self::TYPE_DATETIME_EXAMPLE,
+ ]);
}
/**
* Get Name
- *
- * @return string
*/
public function getName(): string
{
@@ -64,8 +73,6 @@ class Index extends Model
/**
* Get Collection
- *
- * @return string
*/
public function getType(): string
{
diff --git a/src/Appwrite/Utopia/Response/Model/UsageDatabase.php b/src/Appwrite/Utopia/Response/Model/UsageDatabase.php
index d4733f2568..eb985baabb 100644
--- a/src/Appwrite/Utopia/Response/Model/UsageDatabase.php
+++ b/src/Appwrite/Utopia/Response/Model/UsageDatabase.php
@@ -28,6 +28,12 @@ class UsageDatabase extends Model
'default' => 0,
'example' => 0,
])
+ ->addRule('storageTotal', [
+ 'type' => self::TYPE_INTEGER,
+ 'description' => 'Total aggregated number of total storage used in bytes.',
+ 'default' => 0,
+ 'example' => 0,
+ ])
->addRule('collections', [
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated number of collections per period.',
@@ -42,6 +48,13 @@ class UsageDatabase extends Model
'example' => [],
'array' => true
])
+ ->addRule('storage', [
+ 'type' => Response::MODEL_METRIC,
+ 'description' => 'Aggregated storage used in bytes per period.',
+ 'default' => [],
+ 'example' => [],
+ 'array' => true
+ ])
;
}
diff --git a/src/Appwrite/Utopia/Response/Model/UsageDatabases.php b/src/Appwrite/Utopia/Response/Model/UsageDatabases.php
index f775f9489d..e0abba8ab8 100644
--- a/src/Appwrite/Utopia/Response/Model/UsageDatabases.php
+++ b/src/Appwrite/Utopia/Response/Model/UsageDatabases.php
@@ -34,6 +34,12 @@ class UsageDatabases extends Model
'default' => 0,
'example' => 0,
])
+ ->addRule('storageTotal', [
+ 'type' => self::TYPE_INTEGER,
+ 'description' => 'Total aggregated number of total databases storage in bytes.',
+ 'default' => 0,
+ 'example' => 0,
+ ])
->addRule('databases', [
'type' => Response::MODEL_METRIC,
'description' => 'Aggregated number of databases per period.',
@@ -55,6 +61,13 @@ class UsageDatabases extends Model
'example' => [],
'array' => true
])
+ ->addRule('storage', [
+ 'type' => Response::MODEL_METRIC,
+ 'description' => 'An array of the aggregated number of databases storage in bytes per period.',
+ 'default' => [],
+ 'example' => [],
+ 'array' => true
+ ])
;
}
diff --git a/src/Appwrite/Utopia/Response/Model/UsageProject.php b/src/Appwrite/Utopia/Response/Model/UsageProject.php
index 2703691238..17d9271f04 100644
--- a/src/Appwrite/Utopia/Response/Model/UsageProject.php
+++ b/src/Appwrite/Utopia/Response/Model/UsageProject.php
@@ -28,6 +28,12 @@ class UsageProject extends Model
'default' => 0,
'example' => 0,
])
+ ->addRule('databasesStorageTotal', [
+ 'type' => self::TYPE_INTEGER,
+ 'description' => 'Total aggregated sum of databases storage size (in bytes).',
+ 'default' => 0,
+ 'example' => 0,
+ ])
->addRule('usersTotal', [
'type' => self::TYPE_INTEGER,
'description' => 'Total aggregated number of users.',
@@ -118,6 +124,13 @@ class UsageProject extends Model
'example' => [],
'array' => true
])
+ ->addRule('databasesStorageBreakdown', [
+ 'type' => Response::MODEL_METRIC_BREAKDOWN,
+ 'description' => 'An array of the aggregated breakdown of storage usage by databases.',
+ 'default' => [],
+ 'example' => [],
+ 'array' => true
+ ])
->addRule('executionsMbSecondsBreakdown', [
'type' => Response::MODEL_METRIC_BREAKDOWN,
'description' => 'Aggregated breakdown in totals of execution mbSeconds by functions.',
diff --git a/src/Appwrite/Utopia/Response/Models.php b/src/Appwrite/Utopia/Response/Models.php
deleted file mode 100644
index 2a0393321c..0000000000
--- a/src/Appwrite/Utopia/Response/Models.php
+++ /dev/null
@@ -1,304 +0,0 @@
-getType()] = $instance;
- }
-
- /**
- * Get Model Object
- *
- * @param string $key
- * @return Model
- * @throws Exception
- */
- public static function getModel(string $key): Model
- {
- if (!isset(self::$models[$key])) {
- throw new Exception('Undefined model: ' . $key);
- }
-
- return self::$models[$key];
- }
-
- public static function getModels(): array
- {
- return self::$models;
- }
-}
diff --git a/src/Appwrite/Utopia/View.php b/src/Appwrite/Utopia/View.php
index 60cafb1ca8..e4ed8164c9 100644
--- a/src/Appwrite/Utopia/View.php
+++ b/src/Appwrite/Utopia/View.php
@@ -2,7 +2,7 @@
namespace Appwrite\Utopia;
-use Utopia\View\View as OldView;
+use Utopia\View as OldView;
class View extends OldView
{
diff --git a/tests/e2e/General/HTTPTest.php b/tests/e2e/General/HTTPTest.php
index 0881966365..fe600cd0f8 100644
--- a/tests/e2e/General/HTTPTest.php
+++ b/tests/e2e/General/HTTPTest.php
@@ -133,7 +133,7 @@ class HTTPTest extends Scope
$this->assertEquals(200, $response['headers']['status-code']);
// looks like recent change in the validator
- $this->assertTrue(empty($response['body']['schemaValidationMessages']));
+ $this->assertEmpty($response['body']['schemaValidationMessages'], 'Schema validation failed for ' . $file . ': ' . json_encode($response['body']['schemaValidationMessages'], JSON_PRETTY_PRINT));
}
}
diff --git a/tests/e2e/General/UsageTest.php b/tests/e2e/General/UsageTest.php
index d3623acffc..c50a15fd3f 100644
--- a/tests/e2e/General/UsageTest.php
+++ b/tests/e2e/General/UsageTest.php
@@ -143,7 +143,7 @@ class UsageTest extends Scope
);
$this->assertEquals(200, $response['headers']['status-code']);
- $this->assertEquals(20, count($response['body']));
+ $this->assertEquals(22, count($response['body']));
$this->validateDates($response['body']['network']);
$this->validateDates($response['body']['requests']);
$this->validateDates($response['body']['users']);
@@ -324,7 +324,7 @@ class UsageTest extends Scope
]
);
- $this->assertEquals(20, count($response['body']));
+ $this->assertEquals(22, count($response['body']));
$this->assertEquals(1, count($response['body']['requests']));
$this->assertEquals($requestsTotal, $response['body']['requests'][array_key_last($response['body']['requests'])]['value']);
$this->validateDates($response['body']['requests']);
@@ -545,7 +545,7 @@ class UsageTest extends Scope
]
);
- $this->assertEquals(20, count($response['body']));
+ $this->assertEquals(22, count($response['body']));
$this->assertEquals(1, count($response['body']['requests']));
$this->assertEquals(1, count($response['body']['network']));
$this->assertEquals($requestsTotal, $response['body']['requests'][array_key_last($response['body']['requests'])]['value']);
@@ -590,6 +590,260 @@ class UsageTest extends Scope
return $data;
}
+ public function testDatabaseStoragePrepare(): array
+ {
+ $response = $this->client->call(
+ Client::METHOD_POST,
+ '/databases',
+ array_merge([
+ 'content-type' => 'application/json',
+ 'x-appwrite-project' => $this->getProject()['$id']
+ ], $this->getHeaders()),
+ [
+ 'databaseId' => 'unique()',
+ 'name' => 'dbStorageStats',
+ ]
+ );
+
+ $this->assertNotEmpty($response['body']['$id']);
+ $databaseId = $response['body']['$id'];
+
+ $response = $this->client->call(
+ Client::METHOD_POST,
+ '/databases/' . $databaseId . '/collections',
+ array_merge([
+ 'content-type' => 'application/json',
+ 'x-appwrite-project' => $this->getProject()['$id']
+ ], $this->getHeaders()),
+ [
+ 'collectionId' => 'unique()',
+ 'name' => 'collectionStorageStats',
+ 'documentSecurity' => false,
+ 'permissions' => [
+ Permission::read(Role::any()),
+ Permission::create(Role::any()),
+ Permission::update(Role::any()),
+ Permission::delete(Role::any()),
+ ],
+ ]
+ );
+
+ $this->assertNotEmpty($response['body']['$id']);
+ $collectionId = $response['body']['$id'];
+
+ $response = $this->client->call(
+ Client::METHOD_POST,
+ '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes' . '/string',
+ array_merge([
+ 'content-type' => 'application/json',
+ 'x-appwrite-project' => $this->getProject()['$id']
+ ], $this->getHeaders()),
+ [
+ 'key' => 'data',
+ 'size' => 100000,
+ 'required' => true,
+ ]
+ );
+
+ return [
+ 'databaseId' => $databaseId,
+ 'collectionId' => $collectionId,
+ ];
+ }
+
+ // /** @depends testDatabaseStoragePrepare */
+ // #[Retry(count: 1)]
+ // public function testDatabaseStorageStatsCreateDocument(array $data): array
+ // {
+ // $databaseId = $data['databaseId'];
+ // $collectionId = $data['collectionId'];
+
+ // $originalProjectMetrics = $this->client->call(
+ // Client::METHOD_GET,
+ // '/project/usage',
+ // $this->getConsoleHeaders(),
+ // [
+ // 'period' => '1d',
+ // 'startDate' => self::getToday(),
+ // 'endDate' => self::getTomorrow(),
+ // ]
+ // );
+
+ // $this->assertEquals(200, $originalProjectMetrics['headers']['status-code']);
+ // $this->assertArrayHasKey('databasesStorageTotal', $originalProjectMetrics['body']);
+
+ // $originalProjectMetrics = $originalProjectMetrics['body'];
+
+ // $originalDatabaseMetrics = $this->client->call(
+ // Client::METHOD_GET,
+ // '/databases/' . $databaseId . '/usage?range=30d',
+ // $this->getConsoleHeaders()
+ // );
+
+ // $this->assertEquals(200, $originalDatabaseMetrics['headers']['status-code']);
+ // $this->assertArrayHasKey('storageTotal', $originalDatabaseMetrics['body']);
+ // $originalDatabaseMetrics = $originalDatabaseMetrics['body'];
+
+ // // Create documents
+ // for ($i = 0; $i < 100; $i++) {
+ // $response = $this->client->call(
+ // Client::METHOD_POST,
+ // '/databases/' . $databaseId . '/collections/' . $collectionId . '/documents',
+ // array_merge([
+ // 'content-type' => 'application/json',
+ // 'x-appwrite-project' => $this->getProject()['$id']
+ // ], $this->getHeaders()),
+ // [
+ // 'documentId' => 'unique()',
+ // 'data' => ['data' => str_repeat('a', 10000)],
+ // ]
+ // );
+
+ // $this->assertEquals(201, $response['headers']['status-code']);
+ // }
+
+ // sleep(self::WAIT);
+
+ // for ($i = 0; $i < 3; $i++) {
+ // try {
+ // $newProjectMetrics = $this->client->call(
+ // Client::METHOD_GET,
+ // '/project/usage',
+ // $this->getConsoleHeaders(),
+ // [
+ // 'period' => '1d',
+ // 'startDate' => self::getToday(),
+ // 'endDate' => self::getTomorrow(),
+ // ]
+ // );
+
+ // $this->assertEquals(200, $newProjectMetrics['headers']['status-code']);
+ // $this->assertArrayHasKey('databasesStorageTotal', $newProjectMetrics['body']);
+ // $this->assertGreaterThan($originalProjectMetrics['databasesStorageTotal'], $newProjectMetrics['body']['databasesStorageTotal']);
+
+ // $newProjectMetrics = $newProjectMetrics['body'];
+
+ // $newDatabaseMetrics = $this->client->call(
+ // Client::METHOD_GET,
+ // '/databases/' . $databaseId . '/usage?range=30d',
+ // $this->getConsoleHeaders()
+ // );
+
+ // $this->assertEquals(200, $newDatabaseMetrics['headers']['status-code']);
+ // $this->assertArrayHasKey('storageTotal', $newDatabaseMetrics['body']);
+ // $this->assertGreaterThan($originalDatabaseMetrics['storageTotal'], $newDatabaseMetrics['body']['storageTotal']);
+
+ // $newDatabaseMetrics = $newDatabaseMetrics['body'];
+
+ // return [
+ // 'databaseId' => $databaseId,
+ // 'collectionId' => $collectionId,
+ // 'currentProjectMetrics' => $newProjectMetrics,
+ // 'currentDatabaseMetrics' => $newDatabaseMetrics,
+ // ];
+ // } catch (ExpectationFailedException $e) {
+ // if ($i === 2) {
+ // throw $e;
+ // }
+ // sleep(self::WAIT);
+ // continue;
+ // }
+ // }
+ // }
+
+ // /** @depends testDatabaseStorageStatsCreateDocument */
+ // #[Retry(count: 1)]
+ // public function testDatabaseStorageStatsDeleteDocument(array $data): array
+ // {
+ // $databaseId = $data['databaseId'];
+ // $collectionId = $data['collectionId'];
+ // $currentProjectMetrics = $data['currentProjectMetrics'];
+ // $currentDatabaseMetrics = $data['currentDatabaseMetrics'];
+
+ // $documents = $this->client->call(
+ // Client::METHOD_GET,
+ // '/databases/' . $databaseId . '/collections/' . $collectionId . '/documents',
+ // array_merge([
+ // 'x-appwrite-project' => $this->getProject()['$id']
+ // ], $this->getHeaders()),
+ // [
+ // 'queries' => [
+ // Query::limit(50)->toString()
+ // ]
+ // ]
+ // );
+
+ // foreach ($documents['body']['documents'] as $document) {
+ // $response = $this->client->call(
+ // Client::METHOD_DELETE,
+ // '/databases/' . $databaseId . '/collections/' . $collectionId . '/documents/' . $document['$id'],
+ // array_merge([
+ // 'x-appwrite-project' => $this->getProject()['$id']
+ // ], $this->getHeaders())
+ // );
+
+ // $this->assertEquals(204, $response['headers']['status-code']);
+ // }
+
+ // sleep(self::WAIT);
+
+ // for ($i = 0; $i < 3; $i++) {
+ // try {
+ // $newProjectMetrics = $this->client->call(
+ // Client::METHOD_GET,
+ // '/project/usage',
+ // $this->getConsoleHeaders(),
+ // [
+ // 'period' => '1d',
+ // 'startDate' => self::getToday(),
+ // 'endDate' => self::getTomorrow(),
+ // ]
+ // );
+
+ // $this->assertEquals(200, $newProjectMetrics['headers']['status-code']);
+ // $this->assertArrayHasKey('databasesStorageTotal', $newProjectMetrics['body']);
+ // $this->assertLessThan($currentProjectMetrics['databasesStorageTotal'], $newProjectMetrics['body']['databasesStorageTotal']);
+
+ // $newProjectMetrics = $newProjectMetrics['body'];
+
+ // $newDatabaseMetrics = $this->client->call(
+ // Client::METHOD_GET,
+ // '/databases/' . $databaseId . '/usage?range=30d',
+ // $this->getConsoleHeaders()
+ // );
+
+ // $this->assertEquals(200, $newDatabaseMetrics['headers']['status-code']);
+ // $this->assertArrayHasKey('storageTotal', $newDatabaseMetrics['body']);
+ // $this->assertLessThan($currentDatabaseMetrics['storageTotal'], $newDatabaseMetrics['body']['storageTotal']);
+
+ // $newDatabaseMetrics = $newDatabaseMetrics['body'];
+
+ // return [
+ // 'databaseId' => $databaseId,
+ // 'collectionId' => $collectionId,
+ // 'currentProjectMetrics' => $newProjectMetrics,
+ // 'currentDatabaseMetrics' => $newDatabaseMetrics,
+ // ];
+ // } catch (ExpectationFailedException $e) {
+ // if ($i === 2) {
+ // throw $e;
+ // }
+ // sleep(self::WAIT);
+ // continue;
+ // }
+ // }
+
+ // $newProjectMetrics = $this->client->call(
+ // Client::METHOD_GET,
+ // '/project/usage',
+ // $this->getConsoleHeaders(),
+ // [
+ // 'period' => '1d',
+ // 'startDate' => self::getToday(),
+ // 'endDate' => self::getTomorrow(),
+ // ]
+ // );
+ // }
/** @depends testDatabaseStats */
public function testPrepareFunctionsStats(array $data): array
@@ -629,9 +883,6 @@ class UsageTest extends Scope
$this->assertEquals(201, $response['headers']['status-code']);
$this->assertNotEmpty($response['body']['$id']);
- $code = realpath(__DIR__ . '/../../resources/functions') . "/php/code.tar.gz";
- $this->packageCode('php');
-
$response = $this->client->call(
Client::METHOD_POST,
'/functions/' . $functionId . '/deployments',
@@ -641,8 +892,8 @@ class UsageTest extends Scope
], $this->getHeaders()),
[
'entrypoint' => 'index.php',
- 'code' => new CURLFile($code, 'application/x-gzip', \basename($code)),
- 'activate' => true
+ 'code' => $this->packageFunction('php'),
+ 'activate' => true,
]
);
@@ -680,7 +931,7 @@ class UsageTest extends Scope
'x-appwrite-project' => $this->getProject()['$id']
], $this->getHeaders()),
[
- 'async' => false,
+ 'async' => 'false',
]
);
@@ -704,7 +955,7 @@ class UsageTest extends Scope
'x-appwrite-project' => $this->getProject()['$id']
], $this->getHeaders()),
[
- 'async' => false,
+ 'async' => 'false',
]
);
diff --git a/tests/e2e/Services/Databases/DatabasesBase.php b/tests/e2e/Services/Databases/DatabasesBase.php
index 28c3227979..04f2dbd8c8 100644
--- a/tests/e2e/Services/Databases/DatabasesBase.php
+++ b/tests/e2e/Services/Databases/DatabasesBase.php
@@ -12,7 +12,7 @@ use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Query;
use Utopia\Database\Validator\Datetime as DatetimeValidator;
-use Utopia\Http\Validator\JSON;
+use Utopia\Validator\JSON;
trait DatabasesBase
{
@@ -2869,7 +2869,7 @@ trait DatabasesBase
$this->assertEquals('Invalid document structure: Attribute "floatRange" has invalid format. Value must be a valid range between 1 and 1', $badFloatRange['body']['message']);
$this->assertEquals('Invalid document structure: Attribute "probability" has invalid format. Value must be a valid range between 0 and 1', $badProbability['body']['message']);
$this->assertEquals('Invalid document structure: Attribute "upperBound" has invalid format. Value must be a valid range between -9,223,372,036,854,775,808 and 10', $tooHigh['body']['message']);
- $this->assertEquals('Invalid document structure: Attribute "lowerBound" has invalid format. Value must be a valid range between 5 and '.\number_format(PHP_INT_MAX), $tooLow['body']['message']);
+ $this->assertEquals('Invalid document structure: Attribute "lowerBound" has invalid format. Value must be a valid range between 5 and 9,223,372,036,854,775,807', $tooLow['body']['message']);
}
/**
diff --git a/tests/e2e/Services/Databases/DatabasesConsoleClientTest.php b/tests/e2e/Services/Databases/DatabasesConsoleClientTest.php
index ca77cf2581..96bb0b5609 100644
--- a/tests/e2e/Services/Databases/DatabasesConsoleClientTest.php
+++ b/tests/e2e/Services/Databases/DatabasesConsoleClientTest.php
@@ -224,7 +224,7 @@ class DatabasesConsoleClientTest extends Scope
]);
$this->assertEquals(200, $response['headers']['status-code']);
- $this->assertEquals(5, count($response['body']));
+ $this->assertEquals(7, count($response['body']));
$this->assertEquals('24h', $response['body']['range']);
$this->assertIsNumeric($response['body']['documentsTotal']);
$this->assertIsNumeric($response['body']['collectionsTotal']);
diff --git a/tests/e2e/Services/Databases/DatabasesCustomClientTest.php b/tests/e2e/Services/Databases/DatabasesCustomClientTest.php
index 9b7be063db..8484996058 100644
--- a/tests/e2e/Services/Databases/DatabasesCustomClientTest.php
+++ b/tests/e2e/Services/Databases/DatabasesCustomClientTest.php
@@ -64,10 +64,9 @@ class DatabasesCustomClientTest extends Scope
'required' => true,
]);
- $this->assertEquals(202, $response['headers']['status-code']);
-
sleep(1);
+ $this->assertEquals(202, $response['headers']['status-code']);
// Document aliases write to update, delete
$document1 = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $moviesId . '/documents', array_merge([
@@ -83,7 +82,6 @@ class DatabasesCustomClientTest extends Scope
]
]);
- $this->assertEquals(201, $document1['headers']['status-code']);
$this->assertNotContains(Permission::create(Role::user($this->getUser()['$id'])), $document1['body']['$permissions']);
$this->assertContains(Permission::update(Role::user($this->getUser()['$id'])), $document1['body']['$permissions']);
$this->assertContains(Permission::delete(Role::user($this->getUser()['$id'])), $document1['body']['$permissions']);
diff --git a/tests/e2e/Services/Databases/DatabasesPermissionsGuestTest.php b/tests/e2e/Services/Databases/DatabasesPermissionsGuestTest.php
index 4288b92613..ca8753f374 100644
--- a/tests/e2e/Services/Databases/DatabasesPermissionsGuestTest.php
+++ b/tests/e2e/Services/Databases/DatabasesPermissionsGuestTest.php
@@ -17,14 +17,6 @@ class DatabasesPermissionsGuestTest extends Scope
use SideClient;
use DatabasesPermissionsScope;
- protected Authorization $auth;
-
- public function setUp(): void
- {
- parent::setUp();
- $this->auth = new Authorization();
- }
-
public function createCollection(): array
{
$database = $this->client->call(Client::METHOD_POST, '/databases', array_merge([
@@ -119,8 +111,8 @@ class DatabasesPermissionsGuestTest extends Scope
$this->assertEquals(201, $publicResponse['headers']['status-code']);
$this->assertEquals(201, $privateResponse['headers']['status-code']);
- $roles = $this->auth->getRoles();
- $this->auth->cleanRoles();
+ $roles = Authorization::getRoles();
+ Authorization::cleanRoles();
$publicDocuments = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $publicCollectionId . '/documents', [
'content-type' => 'application/json',
@@ -142,7 +134,7 @@ class DatabasesPermissionsGuestTest extends Scope
}
foreach ($roles as $role) {
- $this->auth->addRole($role);
+ Authorization::setRole($role);
}
}
@@ -153,8 +145,8 @@ class DatabasesPermissionsGuestTest extends Scope
$privateCollectionId = $data['privateCollectionId'];
$databaseId = $data['databaseId'];
- $roles = $this->auth->getRoles();
- $this->auth->cleanRoles();
+ $roles = Authorization::getRoles();
+ Authorization::cleanRoles();
$publicResponse = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $publicCollectionId . '/documents', [
'content-type' => 'application/json',
@@ -230,7 +222,7 @@ class DatabasesPermissionsGuestTest extends Scope
$this->assertEquals(401, $privateDocument['headers']['status-code']);
foreach ($roles as $role) {
- $this->auth->addRole($role);
+ Authorization::setRole($role);
}
}
diff --git a/tests/e2e/Services/Functions/FunctionsBase.php b/tests/e2e/Services/Functions/FunctionsBase.php
index f9898757f7..a1bb8f2b21 100644
--- a/tests/e2e/Services/Functions/FunctionsBase.php
+++ b/tests/e2e/Services/Functions/FunctionsBase.php
@@ -2,233 +2,207 @@
namespace Tests\E2E\Services\Functions;
+use Appwrite\Tests\Async;
+use CURLFile;
use Tests\E2E\Client;
use Utopia\CLI\Console;
trait FunctionsBase
{
- protected string $output = '';
+ use Async;
- protected function packageCode($folder)
+ protected string $stdout = '';
+ protected string $stderr = '';
+
+ protected function setupFunction(mixed $params): string
{
- Console::execute('cd ' . realpath(__DIR__ . "/../../../resources/functions") . "/$folder && tar --exclude code.tar.gz -czf code.tar.gz .", '', $this->output);
+ $function = $this->client->call(Client::METHOD_POST, '/functions', array_merge([
+ 'content-type' => 'application/json',
+ 'x-appwrite-project' => $this->getProject()['$id'],
+ 'x-appwrite-key' => $this->getProject()['apiKey'],
+ ]), $params);
+
+ $this->assertEquals($function['headers']['status-code'], 201, 'Setup function failed with status code: ' . $function['headers']['status-code'] . ' and response: ' . json_encode($function['body'], JSON_PRETTY_PRINT));
+
+ $functionId = $function['body']['$id'];
+
+ return $functionId;
}
- protected function awaitDeploymentIsBuilt($functionId, $deploymentId, $checkForSuccess = true): void
+ protected function setupDeployment(string $functionId, mixed $params): string
{
- while (true) {
- $deployment = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/deployments/' . $deploymentId, [
+ $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', array_merge([
+ 'content-type' => 'multipart/form-data',
+ 'x-appwrite-project' => $this->getProject()['$id'],
+ 'x-appwrite-key' => $this->getProject()['apiKey'],
+ ]), $params);
+ $this->assertEquals($deployment['headers']['status-code'], 202, 'Setup deployment failed with status code: ' . $deployment['headers']['status-code'] . ' and response: ' . json_encode($deployment['body'], JSON_PRETTY_PRINT));
+ $deploymentId = $deployment['body']['$id'] ?? '';
+
+ $this->assertEventually(function () use ($functionId, $deploymentId) {
+ $deployment = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/deployments/' . $deploymentId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
- ]);
+ ]));
+ $this->assertEquals('ready', $deployment['body']['status'], 'Deployment status is not ready, deployment: ' . json_encode($deployment['body'], JSON_PRETTY_PRINT));
+ }, 50000, 500);
- if (
- $deployment['headers']['status-code'] >= 400
- || \in_array($deployment['body']['status'], ['ready', 'failed'])
- ) {
- break;
- }
-
- \sleep(1);
- }
-
- if ($checkForSuccess) {
- $this->assertEquals(200, $deployment['headers']['status-code']);
- $this->assertEquals('ready', $deployment['body']['status'], \json_encode($deployment['body']));
- }
+ return $deploymentId;
}
- // /**
- // * @depends testCreateTeam
- // */
- // public function testGetTeam($data):array
- // {
- // $id = $data['teamUid'] ?? '';
+ protected function cleanupFunction(string $functionId): void
+ {
+ $function = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, array_merge([
+ 'content-type' => 'application/json',
+ 'x-appwrite-project' => $this->getProject()['$id'],
+ 'x-appwrite-key' => $this->getProject()['apiKey'],
+ ]));
- // /**
- // * Test for SUCCESS
- // */
- // $response = $this->client->call(Client::METHOD_GET, '/teams/'.$id, array_merge([
- // 'content-type' => 'application/json',
- // 'x-appwrite-project' => $this->getProject()['$id'],
- // ], $this->getHeaders()));
+ $this->assertEquals($function['headers']['status-code'], 204);
+ }
- // $this->assertEquals(200, $response['headers']['status-code']);
- // $this->assertNotEmpty($response['body']['$id']);
- // $this->assertEquals('Arsenal', $response['body']['name']);
- // $this->assertGreaterThan(-1, $response['body']['total']);
- // $this->assertIsInt($response['body']['total']);
- // $this->assertIsInt($response['body']['dateCreated']);
+ protected function createFunction(mixed $params): mixed
+ {
+ $function = $this->client->call(Client::METHOD_POST, '/functions', array_merge([
+ 'content-type' => 'application/json',
+ 'x-appwrite-project' => $this->getProject()['$id'],
+ ], $this->getHeaders()), $params);
- // /**
- // * Test for FAILURE
- // */
+ return $function;
+ }
- // return [];
- // }
+ protected function createVariable(string $functionId, mixed $params): mixed
+ {
+ $variable = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/variables', array_merge([
+ 'content-type' => 'application/json',
+ 'x-appwrite-project' => $this->getProject()['$id'],
+ ], $this->getHeaders()), $params);
- // /**
- // * @depends testCreateTeam
- // */
- // public function testListTeams($data):array
- // {
- // /**
- // * Test for SUCCESS
- // */
- // $response = $this->client->call(Client::METHOD_GET, '/teams', array_merge([
- // 'content-type' => 'application/json',
- // 'x-appwrite-project' => $this->getProject()['$id'],
- // ], $this->getHeaders()));
+ return $variable;
+ }
- // $this->assertEquals(200, $response['headers']['status-code']);
- // $this->assertGreaterThan(0, $response['body']['total']);
- // $this->assertIsInt($response['body']['total']);
- // $this->assertCount(3, $response['body']['teams']);
+ protected function getFunction(string $functionId): mixed
+ {
+ $function = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId, array_merge([
+ 'content-type' => 'application/json',
+ 'x-appwrite-project' => $this->getProject()['$id'],
+ ], $this->getHeaders()));
- // $response = $this->client->call(Client::METHOD_GET, '/teams', array_merge([
- // 'content-type' => 'application/json',
- // 'x-appwrite-project' => $this->getProject()['$id'],
- // ], $this->getHeaders()), [
- // 'limit' => 2,
- // ]);
+ return $function;
+ }
- // $this->assertEquals(200, $response['headers']['status-code']);
- // $this->assertGreaterThan(0, $response['body']['total']);
- // $this->assertIsInt($response['body']['total']);
- // $this->assertCount(2, $response['body']['teams']);
+ protected function getDeployment(string $functionId, string $deploymentId): mixed
+ {
+ $deployment = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/deployments/' . $deploymentId, array_merge([
+ 'content-type' => 'application/json',
+ 'x-appwrite-project' => $this->getProject()['$id'],
+ ], $this->getHeaders()));
- // $response = $this->client->call(Client::METHOD_GET, '/teams', array_merge([
- // 'content-type' => 'application/json',
- // 'x-appwrite-project' => $this->getProject()['$id'],
- // ], $this->getHeaders()), [
- // 'offset' => 1,
- // ]);
+ return $deployment;
+ }
- // $this->assertEquals(200, $response['headers']['status-code']);
- // $this->assertGreaterThan(0, $response['body']['total']);
- // $this->assertIsInt($response['body']['total']);
- // $this->assertCount(2, $response['body']['teams']);
+ protected function getExecution(string $functionId, $executionId): mixed
+ {
+ $execution = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions/' . $executionId, array_merge([
+ 'content-type' => 'application/json',
+ 'x-appwrite-project' => $this->getProject()['$id'],
+ ], $this->getHeaders()));
- // $response = $this->client->call(Client::METHOD_GET, '/teams', array_merge([
- // 'content-type' => 'application/json',
- // 'x-appwrite-project' => $this->getProject()['$id'],
- // ], $this->getHeaders()), [
- // 'search' => 'Manchester',
- // ]);
+ return $execution;
+ }
- // $this->assertEquals(200, $response['headers']['status-code']);
- // $this->assertGreaterThan(0, $response['body']['total']);
- // $this->assertIsInt($response['body']['total']);
- // $this->assertCount(1, $response['body']['teams']);
- // $this->assertEquals('Manchester United', $response['body']['teams'][0]['name']);
+ protected function listFunctions(mixed $params = []): mixed
+ {
+ $functions = $this->client->call(Client::METHOD_GET, '/functions', array_merge([
+ 'content-type' => 'application/json',
+ 'x-appwrite-project' => $this->getProject()['$id'],
+ ], $this->getHeaders()), $params);
- // $response = $this->client->call(Client::METHOD_GET, '/teams', array_merge([
- // 'content-type' => 'application/json',
- // 'x-appwrite-project' => $this->getProject()['$id'],
- // ], $this->getHeaders()), [
- // 'search' => 'United',
- // ]);
+ return $functions;
+ }
- // $this->assertEquals(200, $response['headers']['status-code']);
- // $this->assertGreaterThan(0, $response['body']['total']);
- // $this->assertIsInt($response['body']['total']);
- // $this->assertCount(1, $response['body']['teams']);
- // $this->assertEquals('Manchester United', $response['body']['teams'][0]['name']);
+ protected function listDeployments(string $functionId, $params = []): mixed
+ {
+ $deployments = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/deployments', array_merge([
+ 'content-type' => 'application/json',
+ 'x-appwrite-project' => $this->getProject()['$id'],
+ ], $this->getHeaders()), $params);
- // /**
- // * Test for FAILURE
- // */
+ return $deployments;
+ }
- // return [];
- // }
+ protected function listExecutions(string $functionId, mixed $params = []): mixed
+ {
+ $executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', array_merge([
+ 'content-type' => 'application/json',
+ 'x-appwrite-project' => $this->getProject()['$id'],
+ ], $this->getHeaders()), $params);
- // public function testUpdateTeam():array
- // {
- // /**
- // * Test for SUCCESS
- // */
- // $response = $this->client->call(Client::METHOD_POST, '/teams', array_merge([
- // 'content-type' => 'application/json',
- // 'x-appwrite-project' => $this->getProject()['$id'],
- // ], $this->getHeaders()), [
- // 'name' => 'Demo'
- // ]);
+ return $executions;
+ }
- // $this->assertEquals(201, $response['headers']['status-code']);
- // $this->assertNotEmpty($response['body']['$id']);
- // $this->assertEquals('Demo', $response['body']['name']);
- // $this->assertGreaterThan(-1, $response['body']['total']);
- // $this->assertIsInt($response['body']['total']);
- // $this->assertIsInt($response['body']['dateCreated']);
+ protected function packageFunction(string $function): CURLFile
+ {
+ $folderPath = realpath(__DIR__ . '/../../../resources/functions') . "/$function";
+ $tarPath = "$folderPath/code.tar.gz";
- // $response = $this->client->call(Client::METHOD_PUT, '/teams/'.$response['body']['$id'], array_merge([
- // 'content-type' => 'application/json',
- // 'x-appwrite-project' => $this->getProject()['$id'],
- // ], $this->getHeaders()), [
- // 'name' => 'Demo New'
- // ]);
+ Console::execute("cd $folderPath && tar --exclude code.tar.gz -czf code.tar.gz .", '', $this->stdout, $this->stderr);
- // $this->assertEquals(200, $response['headers']['status-code']);
- // $this->assertNotEmpty($response['body']['$id']);
- // $this->assertEquals('Demo New', $response['body']['name']);
- // $this->assertGreaterThan(-1, $response['body']['total']);
- // $this->assertIsInt($response['body']['total']);
- // $this->assertIsInt($response['body']['dateCreated']);
+ if (filesize($tarPath) > 1024 * 1024 * 5) {
+ throw new \Exception('Code package is too large. Use the chunked upload method instead.');
+ }
- // /**
- // * Test for FAILURE
- // */
- // $response = $this->client->call(Client::METHOD_PUT, '/teams/'.$response['body']['$id'], array_merge([
- // 'content-type' => 'application/json',
- // 'x-appwrite-project' => $this->getProject()['$id'],
- // ], $this->getHeaders()), [
- // ]);
+ return new CURLFile($tarPath, 'application/x-gzip', \basename($tarPath));
+ }
- // $this->assertEquals(400, $response['headers']['status-code']);
+ protected function createDeployment(string $functionId, mixed $params = []): mixed
+ {
+ $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', array_merge([
+ 'content-type' => 'multipart/form-data',
+ 'x-appwrite-project' => $this->getProject()['$id'],
+ ], $this->getHeaders()), $params);
- // return [];
- // }
+ return $deployment;
+ }
- // public function testDeleteTeam():array
- // {
- // /**
- // * Test for SUCCESS
- // */
- // $response = $this->client->call(Client::METHOD_POST, '/teams', array_merge([
- // 'content-type' => 'application/json',
- // 'x-appwrite-project' => $this->getProject()['$id'],
- // ], $this->getHeaders()), [
- // 'name' => 'Demo'
- // ]);
+ protected function getFunctionUsage(string $functionId, mixed $params): mixed
+ {
+ $usage = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/usage', array_merge([
+ 'content-type' => 'application/json',
+ 'x-appwrite-project' => $this->getProject()['$id'],
+ ], $this->getHeaders()), $params);
- // $teamUid = $response['body']['$id'];
+ return $usage;
+ }
- // $this->assertEquals(201, $response['headers']['status-code']);
- // $this->assertNotEmpty($response['body']['$id']);
- // $this->assertEquals('Demo', $response['body']['name']);
- // $this->assertGreaterThan(-1, $response['body']['total']);
- // $this->assertIsInt($response['body']['total']);
- // $this->assertIsInt($response['body']['dateCreated']);
+ protected function getTemplate(string $templateId)
+ {
+ $template = $this->client->call(Client::METHOD_GET, '/functions/templates/' . $templateId, array_merge([
+ 'content-type' => 'application/json',
+ 'x-appwrite-project' => $this->getProject()['$id'],
+ ], $this->getHeaders()));
- // $response = $this->client->call(Client::METHOD_DELETE, '/teams/'.$teamUid, array_merge([
- // 'content-type' => 'application/json',
- // 'x-appwrite-project' => $this->getProject()['$id'],
- // ], $this->getHeaders()));
+ return $template;
+ }
- // $this->assertEquals(204, $response['headers']['status-code']);
- // $this->assertEmpty($response['body']);
+ protected function createExecution(string $functionId, mixed $params = []): mixed
+ {
+ $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([
+ 'content-type' => 'application/json',
+ 'x-appwrite-project' => $this->getProject()['$id'],
+ ], $this->getHeaders()), $params);
- // /**
- // * Test for FAILURE
- // */
- // $response = $this->client->call(Client::METHOD_GET, '/teams/'.$teamUid, array_merge([
- // 'content-type' => 'application/json',
- // 'x-appwrite-project' => $this->getProject()['$id'],
- // ], $this->getHeaders()));
+ return $execution;
+ }
- // $this->assertEquals(404, $response['headers']['status-code']);
+ protected function deleteFunction(string $functionId): mixed
+ {
+ $function = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, array_merge([
+ 'content-type' => 'application/json',
+ 'x-appwrite-project' => $this->getProject()['$id'],
+ ], $this->getHeaders()));
- // return [];
- // }
+ return $function;
+ }
}
diff --git a/tests/e2e/Services/Functions/FunctionsConsoleClientTest.php b/tests/e2e/Services/Functions/FunctionsConsoleClientTest.php
index 8cb7f6f869..3a02cbcba2 100644
--- a/tests/e2e/Services/Functions/FunctionsConsoleClientTest.php
+++ b/tests/e2e/Services/Functions/FunctionsConsoleClientTest.php
@@ -13,13 +13,11 @@ class FunctionsConsoleClientTest extends Scope
{
use ProjectCustom;
use SideConsole;
+ use FunctionsBase;
public function testCreateFunction(): array
{
- $function = $this->client->call(Client::METHOD_POST, '/functions', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
+ $function = $this->createFunction([
'functionId' => ID::unique(),
'name' => 'Test',
'execute' => [Role::user($this->getUser()['$id'])->toString()],
@@ -35,10 +33,9 @@ class FunctionsConsoleClientTest extends Scope
$this->assertEquals(201, $function['headers']['status-code']);
- $response = $this->client->call(Client::METHOD_POST, '/functions', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
+ $functionId = $function['body']['$id'];
+
+ $function2 = $this->createFunction([
'functionId' => ID::unique(),
'name' => 'Test Failure',
'execute' => ['some-random-string'],
@@ -46,73 +43,59 @@ class FunctionsConsoleClientTest extends Scope
'entrypoint' => 'index.php',
]);
- $this->assertEquals(400, $response['headers']['status-code']);
+ $this->assertEquals(400, $function2['headers']['status-code']);
return [
- 'functionId' => $function['body']['$id']
+ 'functionId' => $functionId,
];
}
/**
* @depends testCreateFunction
*/
- public function testGetCollectionUsage(array $data)
+ public function testFunctionUsage(array $data)
{
- /**
- * Test for FAILURE
- */
-
- $response = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/usage', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id']
- ], $this->getHeaders()), [
- 'range' => '232h'
- ]);
-
- $this->assertEquals(400, $response['headers']['status-code']);
-
- $response = $this->client->call(Client::METHOD_GET, '/functions/randomFunctionId/usage', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id']
- ], $this->getHeaders()), [
- 'range' => '24h'
- ]);
-
- $this->assertEquals(404, $response['headers']['status-code']);
-
/**
* Test for SUCCESS
*/
-
- $response = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/usage', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id']
- ], $this->getHeaders()), [
+ $usage = $this->getFunctionUsage($data['functionId'], [
'range' => '24h'
]);
+ $this->assertEquals(200, $usage['headers']['status-code']);
+ $this->assertEquals(19, count($usage['body']));
+ $this->assertEquals('24h', $usage['body']['range']);
+ $this->assertIsNumeric($usage['body']['deploymentsTotal']);
+ $this->assertIsNumeric($usage['body']['deploymentsStorageTotal']);
+ $this->assertIsNumeric($usage['body']['buildsTotal']);
+ $this->assertIsNumeric($usage['body']['buildsStorageTotal']);
+ $this->assertIsNumeric($usage['body']['buildsTimeTotal']);
+ $this->assertIsNumeric($usage['body']['buildsMbSecondsTotal']);
+ $this->assertIsNumeric($usage['body']['executionsTotal']);
+ $this->assertIsNumeric($usage['body']['executionsTimeTotal']);
+ $this->assertIsNumeric($usage['body']['executionsMbSecondsTotal']);
+ $this->assertIsArray($usage['body']['deployments']);
+ $this->assertIsArray($usage['body']['deploymentsStorage']);
+ $this->assertIsArray($usage['body']['builds']);
+ $this->assertIsArray($usage['body']['buildsTime']);
+ $this->assertIsArray($usage['body']['buildsStorage']);
+ $this->assertIsArray($usage['body']['buildsTime']);
+ $this->assertIsArray($usage['body']['buildsMbSeconds']);
+ $this->assertIsArray($usage['body']['executions']);
+ $this->assertIsArray($usage['body']['executionsTime']);
+ $this->assertIsArray($usage['body']['executionsMbSeconds']);
- $this->assertEquals(200, $response['headers']['status-code']);
- $this->assertEquals(19, count($response['body']));
- $this->assertEquals('24h', $response['body']['range']);
- $this->assertIsNumeric($response['body']['deploymentsTotal']);
- $this->assertIsNumeric($response['body']['deploymentsStorageTotal']);
- $this->assertIsNumeric($response['body']['buildsTotal']);
- $this->assertIsNumeric($response['body']['buildsStorageTotal']);
- $this->assertIsNumeric($response['body']['buildsTimeTotal']);
- $this->assertIsNumeric($response['body']['buildsMbSecondsTotal']);
- $this->assertIsNumeric($response['body']['executionsTotal']);
- $this->assertIsNumeric($response['body']['executionsTimeTotal']);
- $this->assertIsNumeric($response['body']['executionsMbSecondsTotal']);
- $this->assertIsArray($response['body']['deployments']);
- $this->assertIsArray($response['body']['deploymentsStorage']);
- $this->assertIsArray($response['body']['builds']);
- $this->assertIsArray($response['body']['buildsTime']);
- $this->assertIsArray($response['body']['buildsStorage']);
- $this->assertIsArray($response['body']['buildsTime']);
- $this->assertIsArray($response['body']['buildsMbSeconds']);
- $this->assertIsArray($response['body']['executions']);
- $this->assertIsArray($response['body']['executionsTime']);
- $this->assertIsArray($response['body']['executionsMbSeconds']);
+ /**
+ * Test for FAILURE
+ */
+ $usage = $this->getFunctionUsage($data['functionId'], [
+ 'range' => '232h'
+ ]);
+ $this->assertEquals(400, $usage['headers']['status-code']);
+
+ $usage = $this->getFunctionUsage('randomFunctionId', [
+ 'range' => '24h'
+ ]);
+ $this->assertEquals(404, $usage['headers']['status-code']);
}
/**
@@ -123,31 +106,53 @@ class FunctionsConsoleClientTest extends Scope
/**
* Test for SUCCESS
*/
+ $variable = $this->createVariable(
+ $data['functionId'],
+ [
+ 'key' => 'APP_TEST',
+ 'value' => 'TESTINGVALUE'
+ ]
+ );
- $response = $this->client->call(Client::METHOD_POST, '/functions/' . $data['functionId'] . '/variables', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
- 'key' => 'APP_TEST',
- 'value' => 'TESTINGVALUE'
- ]);
+ $this->assertEquals(201, $variable['headers']['status-code']);
- $this->assertEquals(201, $response['headers']['status-code']);
- $variableId = $response['body']['$id'];
+ $variableId = $variable['body']['$id'];
/**
* Test for FAILURE
*/
+ // Test for duplicate key
+ $variable = $this->createVariable(
+ $data['functionId'],
+ [
+ 'key' => 'APP_TEST',
+ 'value' => 'ANOTHERTESTINGVALUE'
+ ]
+ );
- $response = $this->client->call(Client::METHOD_POST, '/functions/' . $data['functionId'] . '/variables', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
- 'key' => 'APP_TEST',
- 'value' => 'ANOTHER_TESTINGVALUE'
- ]);
+ $this->assertEquals(409, $variable['headers']['status-code']);
- $this->assertEquals(409, $response['headers']['status-code']);
+ // Test for invalid key
+ $variable = $this->createVariable(
+ $data['functionId'],
+ [
+ 'key' => str_repeat("A", 256),
+ 'value' => 'TESTINGVALUE'
+ ]
+ );
+
+ $this->assertEquals(400, $variable['headers']['status-code']);
+
+ // Test for invalid value
+ $variable = $this->createVariable(
+ $data['functionId'],
+ [
+ 'key' => 'LONGKEY',
+ 'value' => str_repeat("#", 8193),
+ ]
+ );
+
+ $this->assertEquals(400, $variable['headers']['status-code']);
return array_merge(
$data,
@@ -155,28 +160,6 @@ class FunctionsConsoleClientTest extends Scope
'variableId' => $variableId
]
);
-
- $longKey = str_repeat("A", 256);
- $response = $this->client->call(Client::METHOD_POST, '/functions/' . $data['functionId'] . '/variables', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
- 'key' => $longKey,
- 'value' => 'TESTINGVALUE'
- ]);
-
- $this->assertEquals(400, $response['headers']['status-code']);
-
- $longValue = str_repeat("#", 8193);
- $response = $this->client->call(Client::METHOD_POST, '/functions/' . $data['functionId'] . '/variables', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
- 'key' => 'LONGKEY',
- 'value' => $longValue
- ]);
-
- $this->assertEquals(400, $response['headers']['status-code']);
}
/**
diff --git a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php
index 3c0993be85..31cc05f423 100644
--- a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php
+++ b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php
@@ -2,17 +2,12 @@
namespace Tests\E2E\Services\Functions;
-use Appwrite\Tests\Retry;
-use CURLFile;
use Tests\E2E\Client;
use Tests\E2E\Scopes\ProjectCustom;
use Tests\E2E\Scopes\Scope;
use Tests\E2E\Scopes\SideClient;
-use Utopia\Config\Config;
-use Utopia\Database\Document;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Role;
-use Utopia\Database\Query;
class FunctionsCustomClientTest extends Scope
{
@@ -20,15 +15,12 @@ class FunctionsCustomClientTest extends Scope
use ProjectCustom;
use SideClient;
- public function testCreate(): array
+ public function testCreateFunction()
{
/**
- * Test for SUCCESS
+ * Test for FAILURE
*/
- $response1 = $this->client->call(Client::METHOD_POST, '/functions', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
+ $function = $this->createFunction([
'functionId' => ID::unique(),
'name' => 'Test',
'events' => [
@@ -38,23 +30,15 @@ class FunctionsCustomClientTest extends Scope
'schedule' => '0 0 1 1 *',
'timeout' => 10,
]);
-
- $this->assertEquals(401, $response1['headers']['status-code']);
-
- return [];
+ $this->assertEquals(401, $function['headers']['status-code']);
}
- #[Retry(count: 2)]
- public function testCreateExecution(): array
+ public function testCreateExecution()
{
/**
* Test for SUCCESS
*/
- $function = $this->client->call(Client::METHOD_POST, '/functions', [
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- 'x-appwrite-key' => $this->getProject()['apiKey'],
- ], [
+ $functionId = $this->setupFunction([
'functionId' => ID::unique(),
'name' => 'Test',
'execute' => [Role::user($this->getUser()['$id'])->toString()],
@@ -64,278 +48,40 @@ class FunctionsCustomClientTest extends Scope
'users.*.create',
'users.*.delete',
],
- 'schedule' => '* * * * *', // execute every minute
'timeout' => 10,
]);
-
- $this->assertEquals(201, $function['headers']['status-code']);
-
- /** Create Variables */
- $variable = $this->client->call(Client::METHOD_POST, '/functions/' . $function['body']['$id'] . '/variables', [
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- 'x-appwrite-key' => $this->getProject()['apiKey'],
- ], [
- 'key' => 'funcKey1',
- 'value' => 'funcValue1',
- ]);
-
- $variable2 = $this->client->call(Client::METHOD_POST, '/functions/' . $function['body']['$id'] . '/variables', [
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- 'x-appwrite-key' => $this->getProject()['apiKey'],
- ], [
- 'key' => 'funcKey2',
- 'value' => 'funcValue2',
- ]);
-
- $variable3 = $this->client->call(Client::METHOD_POST, '/functions/' . $function['body']['$id'] . '/variables', [
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- 'x-appwrite-key' => $this->getProject()['apiKey'],
- ], [
- 'key' => 'funcKey3',
- 'value' => 'funcValue3',
- ]);
-
- $this->assertEquals(201, $variable['headers']['status-code']);
- $this->assertEquals(201, $variable2['headers']['status-code']);
- $this->assertEquals(201, $variable3['headers']['status-code']);
-
- $folder = 'php';
- $code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz";
- $this->packageCode($folder);
-
- $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $function['body']['$id'] . '/deployments', [
- 'content-type' => 'multipart/form-data',
- 'x-appwrite-project' => $this->getProject()['$id'],
- 'x-appwrite-key' => $this->getProject()['apiKey'],
- ], [
+ $this->setupDeployment($functionId, [
'entrypoint' => 'index.php',
- 'code' => new CURLFile($code, 'application/x-gzip', \basename($code)),
+ 'code' => $this->packageFunction('php'),
'activate' => true
]);
- $deploymentId = $deployment['body']['$id'] ?? '';
-
- $this->assertEquals(202, $deployment['headers']['status-code']);
-
- $this->awaitDeploymentIsBuilt($function['body']['$id'], $deploymentId);
-
- $function = $this->client->call(Client::METHOD_PATCH, '/functions/' . $function['body']['$id'] . '/deployments/' . $deploymentId, [
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- 'x-appwrite-key' => $this->getProject()['apiKey'],
- ], []);
-
- $this->assertEquals(200, $function['headers']['status-code']);
-
- $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $function['body']['$id'] . '/executions', [
+ // Deny create async execution as guest
+ $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], [
'async' => true,
]);
-
$this->assertEquals(401, $execution['headers']['status-code']);
- $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $function['body']['$id'] . '/executions', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
+ // Allow create async execution as user
+ $execution = $this->createExecution($functionId, [
'async' => true,
]);
-
$this->assertEquals(202, $execution['headers']['status-code']);
- // Wait for the first scheduled execution to be created
- sleep(90);
-
- $executions = $this->client->call(Client::METHOD_GET, '/functions/' . $function['body']['$id'] . '/executions', [
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- 'x-appwrite-key' => $this->getProject()['apiKey'],
- ]);
-
- $this->assertEquals(200, $executions['headers']['status-code']);
- $this->assertCount(2, $executions['body']['executions']);
- $this->assertIsArray($executions['body']['executions']);
- $this->assertEquals($executions['body']['executions'][1]['trigger'], 'schedule');
- $this->assertEquals($executions['body']['executions'][1]['status'], 'completed');
- $this->assertEquals($executions['body']['executions'][1]['responseStatusCode'], 200);
- $this->assertEquals($executions['body']['executions'][1]['responseBody'], '');
- $this->assertEquals($executions['body']['executions'][1]['logs'], '');
- $this->assertGreaterThan(0, $executions['body']['executions'][1]['duration']);
-
- // Cleanup : Delete function
- $response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $function['body']['$id'], [
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- 'x-appwrite-key' => $this->getProject()['apiKey'],
- ], []);
-
- $this->assertEquals(204, $response['headers']['status-code']);
-
- return [];
+ $this->cleanupFunction($functionId);
}
- public function testCreateScheduledExecution(): void
- {
- /**
- * Test for SUCCESS
- */
- $function = $this->client->call(Client::METHOD_POST, '/functions', [
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- 'x-appwrite-key' => $this->getProject()['apiKey'],
- ], [
- 'functionId' => ID::unique(),
- 'name' => 'Test',
- 'execute' => [Role::user($this->getUser()['$id'])->toString()],
- 'runtime' => 'php-8.0',
- 'entrypoint' => 'index.php',
- 'timeout' => 10,
- ]);
- $this->assertEquals(201, $function['headers']['status-code']);
-
- $folder = 'php';
- $code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz";
- $this->packageCode($folder);
-
- $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $function['body']['$id'] . '/deployments', [
- 'content-type' => 'multipart/form-data',
- 'x-appwrite-project' => $this->getProject()['$id'],
- 'x-appwrite-key' => $this->getProject()['apiKey'],
- ], [
- 'entrypoint' => 'index.php',
- 'code' => new CURLFile($code, 'application/x-gzip', \basename($code)),
- 'activate' => true
- ]);
- $deploymentId = $deployment['body']['$id'] ?? '';
- $this->assertEquals(202, $deployment['headers']['status-code']);
-
- $this->awaitDeploymentIsBuilt($function['body']['$id'], $deploymentId, true);
-
- // Schedule execution for the future
- \date_default_timezone_set('UTC');
- $futureTime = (new \DateTime())->add(new \DateInterval('PT2M'));
- $futureTime->setTime($futureTime->format('H'), $futureTime->format('i'), 0, 0);
-
- $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $function['body']['$id'] . '/executions', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
- 'async' => true,
- 'scheduledAt' => $futureTime->format(\DateTime::ATOM),
- 'path' => '/custom',
- 'method' => 'GET'
- ]);
-
- $this->assertEquals(202, $execution['headers']['status-code']);
- $this->assertEquals('scheduled', $execution['body']['status']);
-
- $executionId = $execution['body']['$id'];
-
- sleep(60 + 60 + 15); // up to 1 minute round up, 1 minute schedule postpone, 15s cold start safety
-
- $start = \microtime(true);
- while (true) {
- $execution = $this->client->call(Client::METHOD_GET, '/functions/' . $function['body']['$id'] . '/executions/' . $executionId, [
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- 'x-appwrite-key' => $this->getProject()['apiKey'],
- ]);
-
- if ($execution['body']['status'] === 'completed') {
- break;
- }
-
- if (\microtime(true) - $start > 10) {
- $this->fail('Scheduled execution did not complete with status ' . $execution['body']['status'] . ': ' . \json_encode($execution));
- }
-
- usleep(500000); // 0.5 seconds
- }
-
- $this->assertEquals(200, $execution['headers']['status-code']);
- $this->assertEquals(200, $execution['body']['responseStatusCode']);
- $this->assertEquals('completed', $execution['body']['status']);
- $this->assertEquals('/custom', $execution['body']['requestPath']);
- $this->assertEquals('GET', $execution['body']['requestMethod']);
- $this->assertGreaterThan(0, $execution['body']['duration']);
-
- /* Test for FAILURE */
-
- // Schedule synchronous execution
- $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $function['body']['$id'] . '/executions', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
- 'async' => false,
- 'scheduledAt' => $futureTime->format(\DateTime::ATOM),
- ]);
-
- $this->assertEquals(400, $execution['headers']['status-code']);
-
- // Execution with seconds precision
- $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $function['body']['$id'] . '/executions', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
- 'async' => true,
- 'scheduledAt' => (new \DateTime("2100-12-08 16:12:02"))->format(\DateTime::ATOM)
- ]);
-
- $this->assertEquals(400, $execution['headers']['status-code']);
-
- // Execution with milliseconds precision
- $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $function['body']['$id'] . '/executions', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
- 'async' => true,
- 'scheduledAt' => (new \DateTime("2100-12-08 16:12:02.255"))->format(\DateTime::ATOM)
- ]);
-
- $this->assertEquals(400, $execution['headers']['status-code']);
-
- // Execution too soon
- $futureTime = (new \DateTime())->add(new \DateInterval('PT1M'));
- $futureTime->setTime($futureTime->format('H'), $futureTime->format('i'), 0, 0);
- $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $function['body']['$id'] . '/executions', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
- 'async' => true,
- 'scheduledAt' => $futureTime->format(\DateTime::ATOM),
- ]);
-
- $this->assertEquals(400, $execution['headers']['status-code']);
-
- // Cleanup : Delete function
- $response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $function['body']['$id'], [
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- 'x-appwrite-key' => $this->getProject()['apiKey'],
- ], []);
-
- $this->assertEquals(204, $response['headers']['status-code']);
- }
public function testCreateCustomExecution(): array
{
/**
* Test for SUCCESS
*/
- $projectId = $this->getProject()['$id'];
- $apikey = $this->getProject()['apiKey'];
-
- $function = $this->client->call(Client::METHOD_POST, '/functions', [
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $projectId,
- 'x-appwrite-key' => $apikey,
- ], [
+ $functionId = $this->setupFunction([
'functionId' => ID::unique(),
'name' => 'Test',
'execute' => [Role::any()->toString()],
@@ -343,77 +89,16 @@ class FunctionsCustomClientTest extends Scope
'entrypoint' => 'index.php',
'timeout' => 10,
]);
-
- $functionId = $function['body']['$id'] ?? '';
-
- $this->assertEquals(201, $function['headers']['status-code']);
-
- /** Create Variables */
- $variable = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/variables', [
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $projectId,
- 'x-appwrite-key' => $apikey,
- ], [
- 'key' => 'funcKey1',
- 'value' => 'funcValue1',
- ]);
-
- $variable2 = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/variables', [
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $projectId,
- 'x-appwrite-key' => $apikey,
- ], [
- 'key' => 'funcKey2',
- 'value' => 'funcValue2',
- ]);
-
- $variable3 = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/variables', [
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $projectId,
- 'x-appwrite-key' => $apikey,
- ], [
- 'key' => 'funcKey3',
- 'value' => 'funcValue3',
- ]);
-
- $this->assertEquals(201, $variable['headers']['status-code']);
- $this->assertEquals(201, $variable2['headers']['status-code']);
- $this->assertEquals(201, $variable3['headers']['status-code']);
-
- $folder = 'php-fn';
- $code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz";
- $this->packageCode($folder);
-
- $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', [
- 'content-type' => 'multipart/form-data',
- 'x-appwrite-project' => $projectId,
- 'x-appwrite-key' => $apikey,
- ], [
+ $deploymentId = $this->setupDeployment($functionId, [
'entrypoint' => 'index.php',
- 'code' => new CURLFile($code, 'application/x-gzip', \basename($code)), //different tarball names intentional
+ 'code' => $this->packageFunction('php-fn'),
'activate' => true
]);
- $deploymentId = $deployment['body']['$id'] ?? '';
-
- $this->awaitDeploymentIsBuilt($function['body']['$id'], $deploymentId);
-
- $function = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployments/' . $deploymentId, [
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $projectId,
- 'x-appwrite-key' => $apikey,
- ], []);
-
- $this->assertEquals(200, $function['headers']['status-code']);
-
- $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $projectId,
- ], $this->getHeaders()), [
+ $execution = $this->createExecution($functionId, [
'body' => 'foobar',
- 'async' => false
+ 'async' => 'false'
]);
-
$output = json_decode($execution['body']['responseBody'], true);
$this->assertEquals(201, $execution['headers']['status-code']);
$this->assertEquals(200, $execution['body']['responseStatusCode']);
@@ -431,31 +116,20 @@ class FunctionsCustomClientTest extends Scope
$this->assertEquals('foobar', $output['APPWRITE_FUNCTION_DATA']);
$this->assertEquals($this->getUser()['$id'], $output['APPWRITE_FUNCTION_USER_ID']);
$this->assertNotEmpty($output['APPWRITE_FUNCTION_JWT']);
- $this->assertEquals($projectId, $output['APPWRITE_FUNCTION_PROJECT_ID']);
+ $this->assertEquals($this->getProject()['$id'], $output['APPWRITE_FUNCTION_PROJECT_ID']);
- $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $projectId,
- ], $this->getHeaders()), [
+ $execution = $this->createExecution($functionId, [
'body' => 'foobar',
'async' => true
]);
-
+ $executionId = $execution['body']['$id'];
$this->assertEquals(202, $execution['headers']['status-code']);
- $executionId = $execution['body']['$id'] ?? '';
-
- sleep(5);
-
- $execution = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions/' . $executionId, [
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $projectId,
- 'x-appwrite-key' => $apikey,
- ]);
-
- $this->assertEmpty($execution['body']['responseBody']);
- $this->assertEquals(200, $execution['headers']['status-code']);
- $this->assertEquals(200, $execution['body']['responseStatusCode']);
+ $this->assertEventually(function () use ($functionId, $executionId) {
+ $execution = $this->getExecution($functionId, $executionId);
+ $this->assertEquals('completed', $execution['body']['status']);
+ $this->assertEquals(200, $execution['body']['responseStatusCode']);
+ }, 10000, 500);
return [
'functionId' => $functionId
@@ -467,14 +141,7 @@ class FunctionsCustomClientTest extends Scope
/**
* Test for SUCCESS
*/
- $projectId = $this->getProject()['$id'];
- $apikey = $this->getProject()['apiKey'];
-
- $function = $this->client->call(Client::METHOD_POST, '/functions', [
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $projectId,
- 'x-appwrite-key' => $apikey,
- ], [
+ $functionId = $this->setupFunction([
'functionId' => ID::unique(),
'name' => 'Test',
'execute' => [Role::any()->toString()],
@@ -487,56 +154,25 @@ class FunctionsCustomClientTest extends Scope
],
'timeout' => 10,
]);
-
- $functionId = $function['body']['$id'] ?? '';
-
- $this->assertEquals(201, $function['headers']['status-code']);
-
- $folder = 'php-fn';
- $code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz";
- $this->packageCode($folder);
-
- $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', [
- 'content-type' => 'multipart/form-data',
- 'x-appwrite-project' => $projectId,
- 'x-appwrite-key' => $apikey,
- ], [
+ $this->setupDeployment($functionId, [
'entrypoint' => 'index.php',
- 'code' => new CURLFile($code, 'application/x-gzip', \basename($code)), //different tarball names intentional
+ 'code' => $this->packageFunction('php-fn'),
'activate' => true
]);
- $deploymentId = $deployment['body']['$id'] ?? '';
-
- $this->awaitDeploymentIsBuilt($function['body']['$id'], $deploymentId);
-
- // Why do we have to do this?
- $function = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployments/' . $deploymentId, [
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $projectId,
- 'x-appwrite-key' => $apikey,
- ], []);
-
- $this->assertEquals(200, $function['headers']['status-code']);
-
$execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', [
'content-type' => 'application/json',
- 'x-appwrite-project' => $projectId,
+ 'x-appwrite-project' => $this->getProject()['$id'],
], [
'data' => 'foobar',
'async' => true,
]);
-
$this->assertEquals(202, $execution['headers']['status-code']);
}
- public function testCreateExecutionNoDeployment(): array
+ public function testCreateExecutionNoDeployment()
{
- $function = $this->client->call(Client::METHOD_POST, '/functions', [
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- 'x-appwrite-key' => $this->getProject()['apiKey'],
- ], [
+ $functionId = $this->setupFunction([
'functionId' => ID::unique(),
'name' => 'Test',
'execute' => [],
@@ -545,146 +181,18 @@ class FunctionsCustomClientTest extends Scope
'timeout' => 10,
]);
- $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $function['body']['$id'] . '/executions', [
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], [
- 'async' => true,
+ $execution = $this->createExecution($functionId, [
+ 'async' => true
]);
-
$this->assertEquals(404, $execution['headers']['status-code']);
-
- return [];
}
- /**
- * @depends testCreateCustomExecution
- */
- public function testListExecutions(array $data)
- {
- $functionId = $data['functionId'];
- $projectId = $this->getProject()['$id'];
- $apikey = $this->getProject()['apiKey'];
-
- $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $projectId,
- ], $this->getHeaders()), [
- 'data' => 'foobar'
- ]);
-
- $this->assertEquals(201, $execution['headers']['status-code']);
-
- $base = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', [
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $projectId,
- 'x-appwrite-key' => $apikey,
- ]);
-
- $this->assertEquals(200, $base['headers']['status-code']);
- $this->assertCount(3, $base['body']['executions']);
- $this->assertEquals('completed', $base['body']['executions'][0]['status']);
- $this->assertEquals('completed', $base['body']['executions'][1]['status']);
-
- $executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', [
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $projectId,
- 'x-appwrite-key' => $apikey,
- ], [
- 'queries' => [
- Query::limit(1)->toString(),
- ],
- ]);
-
- $this->assertEquals(200, $executions['headers']['status-code']);
- $this->assertCount(1, $executions['body']['executions']);
-
- $executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', [
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $projectId,
- 'x-appwrite-key' => $apikey,
- ], [
- 'queries' => [
- Query::offset(1)->toString(),
- ],
- ]);
-
- $this->assertEquals(200, $executions['headers']['status-code']);
- $this->assertCount(2, $executions['body']['executions']);
-
- $executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', [
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $projectId,
- 'x-appwrite-key' => $apikey,
- ], [
- 'queries' => [
- Query::equal('status', ['completed'])->toString(),
- ],
- ]);
-
- $this->assertEquals(200, $executions['headers']['status-code']);
- $this->assertCount(3, $executions['body']['executions']);
-
- $executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', [
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $projectId,
- 'x-appwrite-key' => $apikey,
- ], [
- 'queries' => [
- Query::equal('status', ['failed'])->toString(),
- ],
- ]);
-
- $this->assertEquals(200, $executions['headers']['status-code']);
- $this->assertCount(0, $executions['body']['executions']);
-
- $executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', [
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $projectId,
- 'x-appwrite-key' => $apikey,
- ], [
- 'queries' => [
- Query::cursorAfter(new Document(['$id' => $base['body']['executions'][0]['$id']]))->toString(),
- ],
- ]);
-
- $this->assertCount(2, $executions['body']['executions']);
- $this->assertEquals($base['body']['executions'][1]['$id'], $executions['body']['executions'][0]['$id']);
-
- $executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', [
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $projectId,
- 'x-appwrite-key' => $apikey,
- ], [
- 'queries' => [
- Query::cursorBefore(new Document(['$id' => $base['body']['executions'][1]['$id']]))->toString(),
- ],
- ]);
-
- // Cleanup : Delete function
- $response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, [
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- 'x-appwrite-key' => $this->getProject()['apiKey'],
- ], []);
-
- $this->assertEquals(204, $response['headers']['status-code']);
- }
-
- public function testSynchronousExecution(): array
+ public function testSynchronousExecution()
{
/**
* Test for SUCCESS
*/
-
- $projectId = $this->getProject()['$id'];
- $apikey = $this->getProject()['apiKey'];
-
- $function = $this->client->call(Client::METHOD_POST, '/functions', [
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $projectId,
- 'x-appwrite-key' => $apikey,
- ], [
+ $functionId = $this->setupFunction([
'functionId' => ID::unique(),
'name' => 'Test',
'execute' => [Role::any()->toString()],
@@ -692,79 +200,16 @@ class FunctionsCustomClientTest extends Scope
'entrypoint' => 'index.php',
'timeout' => 10,
]);
-
- $functionId = $function['body']['$id'] ?? '';
-
- $this->assertEquals(201, $function['headers']['status-code']);
-
- /** Create Variables */
- $variable = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/variables', [
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $projectId,
- 'x-appwrite-key' => $apikey,
- ], [
- 'key' => 'funcKey1',
- 'value' => 'funcValue1',
- ]);
-
- $variable2 = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/variables', [
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $projectId,
- 'x-appwrite-key' => $apikey,
- ], [
- 'key' => 'funcKey2',
- 'value' => 'funcValue2',
- ]);
-
- $variable3 = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/variables', [
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $projectId,
- 'x-appwrite-key' => $apikey,
- ], [
- 'key' => 'funcKey3',
- 'value' => 'funcValue3',
- ]);
-
- $this->assertEquals(201, $variable['headers']['status-code']);
- $this->assertEquals(201, $variable2['headers']['status-code']);
- $this->assertEquals(201, $variable3['headers']['status-code']);
-
- $folder = 'php-fn';
- $code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz";
- $this->packageCode($folder);
-
- $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', [
- 'content-type' => 'multipart/form-data',
- 'x-appwrite-project' => $projectId,
- 'x-appwrite-key' => $apikey,
- ], [
+ $deploymentId = $this->setupDeployment($functionId, [
'entrypoint' => 'index.php',
- 'code' => new CURLFile($code, 'application/x-gzip', \basename($code)), //different tarball names intentional
+ 'code' => $this->packageFunction('php-fn'),
'activate' => true
]);
- $deploymentId = $deployment['body']['$id'] ?? '';
-
- $this->assertEquals(202, $deployment['headers']['status-code']);
-
- $this->awaitDeploymentIsBuilt($function['body']['$id'], $deploymentId);
-
- $function = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployments/' . $deploymentId, [
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $projectId,
- 'x-appwrite-key' => $apikey,
- ]);
-
- $this->assertEquals(200, $function['headers']['status-code']);
-
- $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $projectId,
- ], $this->getHeaders()), [
+ $execution = $this->createExecution($functionId, [
'body' => 'foobar',
- // Testing default value, should be 'async' => false
+ // Testing default value, should be 'async' => 'false'
]);
-
$output = json_decode($execution['body']['responseBody'], true);
$this->assertEquals(201, $execution['headers']['status-code']);
$this->assertEquals('completed', $execution['body']['status']);
@@ -781,67 +226,29 @@ class FunctionsCustomClientTest extends Scope
$this->assertEquals('foobar', $output['APPWRITE_FUNCTION_DATA']);
$this->assertEquals($this->getUser()['$id'], $output['APPWRITE_FUNCTION_USER_ID']);
$this->assertNotEmpty($output['APPWRITE_FUNCTION_JWT']);
- $this->assertEquals($projectId, $output['APPWRITE_FUNCTION_PROJECT_ID']);
+ $this->assertEquals($this->getProject()['$id'], $output['APPWRITE_FUNCTION_PROJECT_ID']);
// Client should never see logs and errors
$this->assertEmpty($execution['body']['logs']);
$this->assertEmpty($execution['body']['errors']);
- // Cleanup : Delete function
- $response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, [
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- 'x-appwrite-key' => $this->getProject()['apiKey'],
- ], []);
-
- $this->assertEquals(204, $response['headers']['status-code']);
-
- return [];
+ $this->cleanupFunction($functionId);
}
- public function testNonOverrideOfHeaders(): array
+ public function testNonOverrideOfHeaders()
{
- /**
- * Test for SUCCESS
- */
- $projectId = $this->getProject()['$id'];
- $apikey = $this->getProject()['apiKey'];
-
- $function = $this->client->call(Client::METHOD_POST, '/functions', [
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $projectId,
- 'x-appwrite-key' => $apikey,
- ], [
+ $functionId = $this->setupFunction([
'functionId' => ID::unique(),
'name' => 'Test',
'execute' => [Role::any()->toString()],
'runtime' => 'node-18.0',
'entrypoint' => 'index.js'
]);
-
- $functionId = $function['body']['$id'] ?? '';
-
- $this->assertEquals(201, $function['headers']['status-code']);
-
- $folder = 'node';
- $code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz";
- $this->packageCode($folder);
-
- $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', [
- 'content-type' => 'multipart/form-data',
- 'x-appwrite-project' => $projectId,
- 'x-appwrite-key' => $apikey,
- ], [
+ $this->setupDeployment($functionId, [
'entrypoint' => 'index.js',
- 'code' => new CURLFile($code, 'application/x-gzip', \basename($code)), //different tarball names intentional
+ 'code' => $this->packageFunction('node'),
'activate' => true
]);
- $deploymentId = $deployment['body']['$id'] ?? '';
-
- $this->assertEquals(202, $deployment['headers']['status-code']);
-
- $this->awaitDeploymentIsBuilt($function['body']['$id'], $deploymentId);
-
$execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
@@ -851,23 +258,13 @@ class FunctionsCustomClientTest extends Scope
'x-appwrite-user-id' => "OVERRIDDEN",
'x-appwrite-user-jwt' => "OVERRIDDEN",
]);
-
$output = json_decode($execution['body']['responseBody'], true);
$this->assertNotEquals('OVERRIDDEN', $output['APPWRITE_FUNCTION_JWT']);
$this->assertNotEquals('OVERRIDDEN', $output['APPWRITE_FUNCTION_EVENT']);
$this->assertNotEquals('OVERRIDDEN', $output['APPWRITE_FUNCTION_TRIGGER']);
$this->assertNotEquals('OVERRIDDEN', $output['APPWRITE_FUNCTION_USER_ID']);
- // Cleanup : Delete function
- $response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, [
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- 'x-appwrite-key' => $this->getProject()['apiKey'],
- ], []);
-
- $this->assertEquals(204, $response['headers']['status-code']);
-
- return [];
+ $this->cleanupFunction($functionId);
}
public function testListTemplates()
@@ -875,7 +272,7 @@ class FunctionsCustomClientTest extends Scope
/**
* Test for SUCCESS
*/
- $expectedTemplates = array_slice(Config::getParam('function-templates', []), 0, 25);
+ // List all templates
$templates = $this->client->call(Client::METHOD_GET, '/functions/templates', array_merge([
'content-type' => 'application/json',
], $this->getHeaders()));
@@ -884,41 +281,35 @@ class FunctionsCustomClientTest extends Scope
$this->assertGreaterThan(0, $templates['body']['total']);
$this->assertIsArray($templates['body']['templates']);
- $this->assertArrayHasKey('runtimes', $templates['body']['templates'][0]);
- $this->assertArrayHasKey('useCases', $templates['body']['templates'][0]);
- for ($i = 0; $i < 25; $i++) {
- $this->assertEquals($expectedTemplates[$i]['name'], $templates['body']['templates'][$i]['name']);
- $this->assertEquals($expectedTemplates[$i]['id'], $templates['body']['templates'][$i]['id']);
- $this->assertEquals($expectedTemplates[$i]['icon'], $templates['body']['templates'][$i]['icon']);
- $this->assertEquals($expectedTemplates[$i]['tagline'], $templates['body']['templates'][$i]['tagline']);
- $this->assertEquals($expectedTemplates[$i]['useCases'], $templates['body']['templates'][$i]['useCases']);
- $this->assertEquals($expectedTemplates[$i]['vcsProvider'], $templates['body']['templates'][$i]['vcsProvider']);
- $this->assertEquals($expectedTemplates[$i]['runtimes'], $templates['body']['templates'][$i]['runtimes']);
- $this->assertEquals($expectedTemplates[$i]['variables'], $templates['body']['templates'][$i]['variables']);
- if (array_key_exists('scopes', $expectedTemplates[$i])) {
- $this->assertEquals($expectedTemplates[$i]['scopes'], $templates['body']['templates'][$i]['scopes']);
- }
+ foreach ($templates['body']['templates'] as $template) {
+ $this->assertArrayHasKey('name', $template);
+ $this->assertArrayHasKey('id', $template);
+ $this->assertArrayHasKey('icon', $template);
+ $this->assertArrayHasKey('tagline', $template);
+ $this->assertArrayHasKey('useCases', $template);
+ $this->assertArrayHasKey('vcsProvider', $template);
+ $this->assertArrayHasKey('runtimes', $template);
+ $this->assertArrayHasKey('variables', $template);
}
- $templates_offset = $this->client->call(Client::METHOD_GET, '/functions/templates', array_merge([
+ // List templates with pagination
+ $templatesOffset = $this->client->call(Client::METHOD_GET, '/functions/templates', array_merge([
'content-type' => 'application/json',
], $this->getHeaders()), [
'limit' => 1,
'offset' => 2
]);
+ $this->assertEquals(200, $templatesOffset['headers']['status-code']);
+ $this->assertEquals(1, $templatesOffset['body']['total']);
+ $this->assertEquals($templates['body']['templates'][2]['id'], $templatesOffset['body']['templates'][0]['id']);
- $this->assertEquals(200, $templates_offset['headers']['status-code']);
- $this->assertEquals(1, $templates_offset['body']['total']);
- // assert that offset works as expected
- $this->assertEquals($templates['body']['templates'][2]['id'], $templates_offset['body']['templates'][0]['id']);
-
+ // List templates with filters
$templates = $this->client->call(Client::METHOD_GET, '/functions/templates', array_merge([
'content-type' => 'application/json',
], $this->getHeaders()), [
'useCases' => ['starter', 'ai'],
'runtimes' => ['bun-1.0', 'dart-2.16']
]);
-
$this->assertEquals(200, $templates['headers']['status-code']);
$this->assertGreaterThanOrEqual(3, $templates['body']['total']);
$this->assertIsArray($templates['body']['templates']);
@@ -928,6 +319,7 @@ class FunctionsCustomClientTest extends Scope
$this->assertArrayHasKey('runtimes', $templates['body']['templates'][0]);
$this->assertContains('bun-1.0', array_column($templates['body']['templates'][0]['runtimes'], 'name'));
+ // List templates with pagination and filters
$templates = $this->client->call(Client::METHOD_GET, '/functions/templates', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
@@ -942,24 +334,26 @@ class FunctionsCustomClientTest extends Scope
$this->assertEquals(5, $templates['body']['total']);
$this->assertIsArray($templates['body']['templates']);
$this->assertArrayHasKey('runtimes', $templates['body']['templates'][0]);
+
foreach ($templates['body']['templates'] as $template) {
$this->assertContains($template['useCases'][0], ['databases']);
}
+
$this->assertContains('node-16.0', array_column($templates['body']['templates'][0]['runtimes'], 'name'));
/**
* Test for FAILURE
*/
+ // List templates with invalid limit
$templates = $this->client->call(Client::METHOD_GET, '/functions/templates', array_merge([
'content-type' => 'application/json',
], $this->getHeaders()), [
'limit' => 5001,
'offset' => 10,
]);
-
$this->assertEquals(400, $templates['headers']['status-code']);
- $this->assertEquals('Invalid `limit` param: Value must be a valid range between 1 and 5,000', $templates['body']['message']);
+ // List templates with invalid offset
$templates = $this->client->call(Client::METHOD_GET, '/functions/templates', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
@@ -967,9 +361,7 @@ class FunctionsCustomClientTest extends Scope
'limit' => 5,
'offset' => 5001,
]);
-
$this->assertEquals(400, $templates['headers']['status-code']);
- $this->assertEquals('Invalid `offset` param: Value must be a valid range between 0 and 5,000', $templates['body']['message']);
}
public function testGetTemplate()
@@ -977,10 +369,7 @@ class FunctionsCustomClientTest extends Scope
/**
* Test for SUCCESS
*/
- $template = $this->client->call(Client::METHOD_GET, '/functions/templates/query-neo4j-auradb', array_merge([
- 'content-type' => 'application/json',
- ], $this->getHeaders()), []);
-
+ $template = $this->getTemplate('query-neo4j-auradb');
$this->assertEquals(200, $template['headers']['status-code']);
$this->assertIsArray($template['body']);
$this->assertEquals('query-neo4j-auradb', $template['body']['id']);
@@ -989,15 +378,13 @@ class FunctionsCustomClientTest extends Scope
$this->assertEquals('Graph database with focus on relations between data.', $template['body']['tagline']);
$this->assertEquals(['databases'], $template['body']['useCases']);
$this->assertEquals('github', $template['body']['vcsProvider']);
+ $this->assertIsArray($template['body']['runtimes']);
+ $this->assertIsArray($template['body']['scopes']);
/**
* Test for FAILURE
*/
- $template = $this->client->call(Client::METHOD_GET, '/functions/templates/invalid-template-id', array_merge([
- 'content-type' => 'application/json',
- ], $this->getHeaders()), []);
-
+ $template = $this->getTemplate('invalid-template-id');
$this->assertEquals(404, $template['headers']['status-code']);
- $this->assertEquals('Function Template with the requested ID could not be found.', $template['body']['message']);
}
}
diff --git a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php
index ded9776582..59a3041a3f 100644
--- a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php
+++ b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php
@@ -3,13 +3,11 @@
namespace Tests\E2E\Services\Functions;
use Appwrite\Functions\Specification;
-use Appwrite\Tests\Retry;
-use CURLFile;
-use PHPUnit\Framework\ExpectationFailedException;
use Tests\E2E\Client;
use Tests\E2E\Scopes\ProjectCustom;
use Tests\E2E\Scopes\Scope;
use Tests\E2E\Scopes\SideServer;
+use Utopia\CLI\Console;
use Utopia\Database\Document;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Role;
@@ -23,15 +21,12 @@ class FunctionsCustomServerTest extends Scope
use ProjectCustom;
use SideServer;
- public function testCreate(): array
+ public function testCreateFunction(): array
{
/**
* Test for SUCCESS
*/
- $response1 = $this->client->call(Client::METHOD_POST, '/functions', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
+ $function = $this->createFunction([
'functionId' => ID::unique(),
'name' => 'Test',
'runtime' => 'php-8.0',
@@ -40,48 +35,35 @@ class FunctionsCustomServerTest extends Scope
'buckets.*.create',
'buckets.*.delete',
],
- 'schedule' => '0 0 1 1 *',
'timeout' => 10,
]);
- $functionId = $response1['body']['$id'] ?? '';
+ $functionId = $functionId = $function['body']['$id'] ?? '';
- $this->assertEquals(201, $response1['headers']['status-code']);
- $this->assertNotEmpty($response1['body']['$id']);
- $this->assertEquals('Test', $response1['body']['name']);
- $this->assertEquals('php-8.0', $response1['body']['runtime']);
$dateValidator = new DatetimeValidator();
- $this->assertEquals(true, $dateValidator->isValid($response1['body']['$createdAt']));
- $this->assertEquals(true, $dateValidator->isValid($response1['body']['$updatedAt']));
- $this->assertEquals('', $response1['body']['deployment']);
+ $this->assertEquals(201, $function['headers']['status-code']);
+ $this->assertNotEmpty($function['body']['$id']);
+ $this->assertEquals('Test', $function['body']['name']);
+ $this->assertEquals('php-8.0', $function['body']['runtime']);
+ $this->assertEquals(true, $dateValidator->isValid($function['body']['$createdAt']));
+ $this->assertEquals(true, $dateValidator->isValid($function['body']['$updatedAt']));
+ $this->assertEquals('', $function['body']['deployment']);
$this->assertEquals([
'buckets.*.create',
'buckets.*.delete',
- ], $response1['body']['events']);
- $this->assertEquals('0 0 1 1 *', $response1['body']['schedule']);
- $this->assertEquals(10, $response1['body']['timeout']);
+ ], $function['body']['events']);
+ $this->assertEmpty($function['body']['schedule']);
+ $this->assertEquals(10, $function['body']['timeout']);
- /** Create Variables */
- $variable = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/variables', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
+ $variable = $this->createVariable($functionId, [
'key' => 'funcKey1',
'value' => 'funcValue1',
]);
-
- $variable2 = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/variables', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
+ $variable2 = $this->createVariable($functionId, [
'key' => 'funcKey2',
'value' => 'funcValue2',
]);
-
- $variable3 = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/variables', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
+ $variable3 = $this->createVariable($functionId, [
'key' => 'funcKey3',
'value' => 'funcValue3',
]);
@@ -90,115 +72,90 @@ class FunctionsCustomServerTest extends Scope
$this->assertEquals(201, $variable2['headers']['status-code']);
$this->assertEquals(201, $variable3['headers']['status-code']);
- /**
- * Test for FAILURE
- */
-
return [
'functionId' => $functionId,
];
}
/**
- * @depends testCreate
+ * @depends testCreateFunction
*/
- public function testList(array $data): array
+ public function testListFunctions(array $data): array
{
/**
* Test for SUCCESS
*/
-
- /**
- * Test search queries
- */
- $response = $this->client->call(Client::METHOD_GET, '/functions', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
+ // Test search id
+ $functions = $this->listFunctions([
'search' => $data['functionId']
]);
- $this->assertEquals($response['headers']['status-code'], 200);
- $this->assertCount(1, $response['body']['functions']);
- $this->assertEquals($response['body']['functions'][0]['name'], 'Test');
+ $this->assertEquals($functions['headers']['status-code'], 200);
+ $this->assertCount(1, $functions['body']['functions']);
+ $this->assertEquals($functions['body']['functions'][0]['name'], 'Test');
- $response = $this->client->call(Client::METHOD_GET, '/functions', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
+ // Test pagination limit
+ $functions = $this->listFunctions([
'queries' => [
Query::limit(1)->toString(),
],
]);
- $this->assertEquals($response['headers']['status-code'], 200);
- $this->assertCount(1, $response['body']['functions']);
+ $this->assertEquals($functions['headers']['status-code'], 200);
+ $this->assertCount(1, $functions['body']['functions']);
- $response = $this->client->call(Client::METHOD_GET, '/functions', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
+ // Test pagination offset
+ $functions = $this->listFunctions([
'queries' => [
Query::offset(1)->toString(),
],
]);
- $this->assertEquals($response['headers']['status-code'], 200);
- $this->assertCount(0, $response['body']['functions']);
+ $this->assertEquals($functions['headers']['status-code'], 200);
+ $this->assertCount(0, $functions['body']['functions']);
- $response = $this->client->call(Client::METHOD_GET, '/functions', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
+ // Test filter enabled
+ $functions = $this->listFunctions([
'queries' => [
Query::equal('enabled', [true])->toString(),
],
]);
- $this->assertEquals($response['headers']['status-code'], 200);
- $this->assertCount(1, $response['body']['functions']);
+ $this->assertEquals($functions['headers']['status-code'], 200);
+ $this->assertCount(1, $functions['body']['functions']);
- $response = $this->client->call(Client::METHOD_GET, '/functions', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
+ // Test filter disabled
+ $functions = $this->listFunctions([
'queries' => [
Query::equal('enabled', [false])->toString(),
],
]);
- $this->assertEquals($response['headers']['status-code'], 200);
- $this->assertCount(0, $response['body']['functions']);
+ $this->assertEquals($functions['headers']['status-code'], 200);
+ $this->assertCount(0, $functions['body']['functions']);
- $response = $this->client->call(Client::METHOD_GET, '/functions', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
+ // Test search name
+ $functions = $this->listFunctions([
'search' => 'Test'
]);
- $this->assertEquals($response['headers']['status-code'], 200);
- $this->assertCount(1, $response['body']['functions']);
- $this->assertEquals($response['body']['functions'][0]['$id'], $data['functionId']);
+ $this->assertEquals($functions['headers']['status-code'], 200);
+ $this->assertCount(1, $functions['body']['functions']);
+ $this->assertEquals($functions['body']['functions'][0]['$id'], $data['functionId']);
- $response = $this->client->call(Client::METHOD_GET, '/functions', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
+ // Test search runtime
+ $functions = $this->listFunctions([
'search' => 'php-8.0'
]);
- $this->assertEquals($response['headers']['status-code'], 200);
- $this->assertCount(1, $response['body']['functions']);
- $this->assertEquals($response['body']['functions'][0]['$id'], $data['functionId']);
+ $this->assertEquals($functions['headers']['status-code'], 200);
+ $this->assertCount(1, $functions['body']['functions']);
+ $this->assertEquals($functions['body']['functions'][0]['$id'], $data['functionId']);
/**
* Test pagination
*/
- $response = $this->client->call(Client::METHOD_POST, '/functions', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
+ $this->setupFunction([
'functionId' => ID::unique(),
'name' => 'Test 2',
'runtime' => 'php-8.0',
@@ -207,44 +164,10 @@ class FunctionsCustomServerTest extends Scope
'buckets.*.create',
'buckets.*.delete',
],
- 'schedule' => '0 0 1 1 *',
'timeout' => 10,
]);
- $this->assertNotEmpty($response['body']['$id']);
- /** Create Variables */
- $variable = $this->client->call(Client::METHOD_POST, '/functions/' . $response['body']['$id'] . '/variables', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
- 'key' => 'funcKey1',
- 'value' => 'funcValue1',
- ]);
-
- $variable2 = $this->client->call(Client::METHOD_POST, '/functions/' . $response['body']['$id'] . '/variables', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
- 'key' => 'funcKey2',
- 'value' => 'funcValue2',
- ]);
-
- $variable3 = $this->client->call(Client::METHOD_POST, '/functions/' . $response['body']['$id'] . '/variables', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
- 'key' => 'funcKey3',
- 'value' => 'funcValue3',
- ]);
-
- $this->assertEquals(201, $variable['headers']['status-code']);
- $this->assertEquals(201, $variable2['headers']['status-code']);
- $this->assertEquals(201, $variable3['headers']['status-code']);
-
- $functions = $this->client->call(Client::METHOD_GET, '/functions', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()));
+ $functions = $this->listFunctions();
$this->assertEquals($functions['headers']['status-code'], 200);
$this->assertEquals($functions['body']['total'], 2);
@@ -253,61 +176,48 @@ class FunctionsCustomServerTest extends Scope
$this->assertEquals($functions['body']['functions'][0]['name'], 'Test');
$this->assertEquals($functions['body']['functions'][1]['name'], 'Test 2');
- $response = $this->client->call(Client::METHOD_GET, '/functions', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
+ $functions1 = $this->listFunctions([
'queries' => [
Query::cursorAfter(new Document(['$id' => $functions['body']['functions'][0]['$id']]))->toString(),
],
]);
- $this->assertEquals($response['headers']['status-code'], 200);
- $this->assertCount(1, $response['body']['functions']);
- $this->assertEquals($response['body']['functions'][0]['name'], 'Test 2');
+ $this->assertEquals($functions1['headers']['status-code'], 200);
+ $this->assertCount(1, $functions1['body']['functions']);
+ $this->assertEquals($functions1['body']['functions'][0]['name'], 'Test 2');
- $response = $this->client->call(Client::METHOD_GET, '/functions', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
+ $functions2 = $this->listFunctions([
'queries' => [
Query::cursorBefore(new Document(['$id' => $functions['body']['functions'][1]['$id']]))->toString(),
],
]);
- $this->assertEquals($response['headers']['status-code'], 200);
- $this->assertCount(1, $response['body']['functions']);
- $this->assertEquals($response['body']['functions'][0]['name'], 'Test');
+ $this->assertEquals($functions2['headers']['status-code'], 200);
+ $this->assertCount(1, $functions2['body']['functions']);
+ $this->assertEquals($functions2['body']['functions'][0]['name'], 'Test');
/**
* Test for FAILURE
*/
- $response = $this->client->call(Client::METHOD_GET, '/functions', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
+ $functions = $this->listFunctions([
'queries' => [
Query::cursorAfter(new Document(['$id' => 'unknown']))->toString(),
],
]);
-
- $this->assertEquals($response['headers']['status-code'], 400);
+ $this->assertEquals($functions['headers']['status-code'], 400);
return $data;
}
/**
- * @depends testList
+ * @depends testListFunctions
*/
- public function testGet(array $data): array
+ public function testGetFunction(array $data): array
{
/**
* Test for SUCCESS
*/
- $function = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'], array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()));
+ $function = $this->getFunction($data['functionId']);
$this->assertEquals($function['headers']['status-code'], 200);
$this->assertEquals($function['body']['name'], 'Test');
@@ -315,10 +225,7 @@ class FunctionsCustomServerTest extends Scope
/**
* Test for FAILURE
*/
- $function = $this->client->call(Client::METHOD_GET, '/functions/x', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()));
+ $function = $this->getFunction('x');
$this->assertEquals($function['headers']['status-code'], 404);
@@ -326,14 +233,14 @@ class FunctionsCustomServerTest extends Scope
}
/**
- * @depends testGet
+ * @depends testGetFunction
*/
- public function testUpdate($data): array
+ public function testUpdateFunction($data): array
{
/**
* Test for SUCCESS
*/
- $response1 = $this->client->call(Client::METHOD_PUT, '/functions/' . $data['functionId'], array_merge([
+ $function = $this->client->call(Client::METHOD_PUT, '/functions/' . $data['functionId'], array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
@@ -348,51 +255,35 @@ class FunctionsCustomServerTest extends Scope
'entrypoint' => 'index.php',
]);
- $this->assertEquals(200, $response1['headers']['status-code']);
- $this->assertNotEmpty($response1['body']['$id']);
- $this->assertEquals('Test1', $response1['body']['name']);
$dateValidator = new DatetimeValidator();
- $this->assertEquals(true, $dateValidator->isValid($response1['body']['$createdAt']));
- $this->assertEquals(true, $dateValidator->isValid($response1['body']['$updatedAt']));
- $this->assertEquals('', $response1['body']['deployment']);
+
+ $this->assertEquals(200, $function['headers']['status-code']);
+ $this->assertNotEmpty($function['body']['$id']);
+ $this->assertEquals('Test1', $function['body']['name']);
+ $this->assertEquals(true, $dateValidator->isValid($function['body']['$createdAt']));
+ $this->assertEquals(true, $dateValidator->isValid($function['body']['$updatedAt']));
+ $this->assertEquals('', $function['body']['deployment']);
$this->assertEquals([
'users.*.update.name',
'users.*.update.email',
- ], $response1['body']['events']);
- $this->assertEquals('0 0 1 1 *', $response1['body']['schedule']);
- $this->assertEquals(15, $response1['body']['timeout']);
+ ], $function['body']['events']);
+ $this->assertEquals('0 0 1 1 *', $function['body']['schedule']);
+ $this->assertEquals(15, $function['body']['timeout']);
- /**
- * Create global variable to test in execution later
- */
- $headers = [
- 'content-type' => 'application/json',
- 'origin' => 'http://localhost',
- 'cookie' => 'a_session_console=' . $this->getRoot()['session'],
- 'x-appwrite-project' => $this->getProject()['$id'],
- 'x-appwrite-mode' => 'admin',
- ];
-
- $variable = $this->client->call(Client::METHOD_POST, '/project/variables', $headers, [
+ // Create a variable for later tests
+ $variable = $this->createVariable($data['functionId'], [
'key' => 'GLOBAL_VARIABLE',
'value' => 'Global Variable Value',
]);
$this->assertEquals(201, $variable['headers']['status-code']);
- /**
- * Test for FAILURE
- */
-
return $data;
}
public function testCreateDeploymentFromCLI()
{
- $function = $this->client->call(Client::METHOD_POST, '/functions', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
+ $functionId = $this->setupFunction([
'functionId' => ID::unique(),
'name' => 'Test',
'execute' => [Role::user($this->getUser()['$id'])->toString()],
@@ -402,148 +293,98 @@ class FunctionsCustomServerTest extends Scope
'users.*.create',
'users.*.delete',
],
- 'schedule' => '0 0 1 1 *',
+ 'schedule' => '0 0 1 1 *', // Once a year
'timeout' => 10,
]);
- $this->assertEquals(201, $function['headers']['status-code']);
-
- $functionId = $function['body']['$id'];
-
- $folder = 'php';
- $code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz";
- $this->packageCode($folder);
-
- $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $function['body']['$id'] . '/deployments', [
+ $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', [
'content-type' => 'multipart/form-data',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
'x-sdk-language' => 'cli',
], [
'entrypoint' => 'index.php',
- 'code' => new CURLFile($code, 'application/x-gzip', \basename($code)),
+ 'code' => $this->packageFunction('php'),
'activate' => true
]);
- $deploymentId = $deployment['body']['$id'] ?? '';
-
$this->assertEquals(202, $deployment['headers']['status-code']);
- $this->awaitDeploymentIsBuilt($function['body']['$id'], $deploymentId);
+ $deploymentId = $deployment['body']['$id'] ?? '';
- $functionDetails = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/deployments/' . $deploymentId, [
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- 'x-appwrite-key' => $this->getProject()['apiKey'],
- ], []);
+ $this->assertEventually(function () use ($functionId, $deploymentId) {
+ $deployment = $this->getDeployment($functionId, $deploymentId);
- $this->assertEquals(200, $functionDetails['headers']['status-code']);
- $this->assertEquals('cli', $functionDetails['body']['type']);
+ $this->assertEquals(200, $deployment['headers']['status-code']);
+ $this->assertEquals('ready', $deployment['body']['status']);
+ $this->assertEquals('cli', $deployment['body']['type']);
+ }, 500000, 1000);
}
- public function testCreateDeploymentFromTemplate()
+ public function testCreateFunctionAndDeploymentFromTemplate()
{
- $runtimeName = 'php-8.0';
- // Fetch starter template (used to create function later)
- $template = $this->client->call(Client::METHOD_GET, '/functions/templates/starter', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()));
+ $starterTemplate = $this->getTemplate('starter');
+ $this->assertEquals(200, $starterTemplate['headers']['status-code']);
- $this->assertEquals(200, $template['headers']['status-code']);
+ $phpRuntime = array_values(array_filter($starterTemplate['body']['runtimes'], function ($runtime) {
+ return $runtime['name'] === 'php-8.0';
+ }))[0];
- $entrypoint = null;
- $rootDirectory = null;
- $commands = null;
- foreach ($template['body']['runtimes'] as $runtime) {
- if ($runtime["name"] !== $runtimeName) {
- continue;
- }
+ // If this fails, the template has variables, and this test needs to be updated
+ $this->assertEmpty($starterTemplate['body']['variables']);
- $entrypoint = $runtime["entrypoint"];
- $rootDirectory = $runtime["providerRootDirectory"];
- $commands = $runtime["commands"];
- break;
- }
-
- $this->assertNotNull($entrypoint);
-
- /**
- * If below test ever starts failing, it means temaplate used in
- * this test now has some variables. This test currently doesnt test variables.
- * Remove bellow assertion and update test to crete variable,
- * and ensure variable works as expected in execution.
- */
- $this->assertEmpty($template['body']['variables']);
- // Create function using settings from template.
- // Deployment is automatically created from template inside endpoint
- $function = $this->client->call(Client::METHOD_POST, '/functions', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- 'x-appwrite-key' => $this->getProject()['apiKey'],
- ], $this->getHeaders()), [
- 'functionId' => ID::unique(),
- 'name' => $template['body']['name'],
- 'runtime' => $runtimeName,
- 'execute' => $template['body']['permissions'],
- 'entrypoint' => $entrypoint,
- 'events' => $template['body']['events'],
- 'schedule' => $template['body']['cron'],
- 'timeout' => $template['body']['timeout'],
- 'commands' => $commands,
- 'scopes' => $template['body']['scopes'],
- 'templateRepository' => $template['body']['providerRepositoryId'],
- 'templateOwner' => $template['body']['providerOwner'],
- 'templateRootDirectory' => $rootDirectory,
- 'templateVersion' => $template['body']['providerVersion'],
- ]);
+ $function = $this->createFunction(
+ [
+ 'functionId' => ID::unique(),
+ 'name' => $starterTemplate['body']['name'],
+ 'runtime' => 'php-8.0',
+ 'execute' => $starterTemplate['body']['permissions'],
+ 'entrypoint' => $phpRuntime['entrypoint'],
+ 'events' => $starterTemplate['body']['events'],
+ 'schedule' => $starterTemplate['body']['cron'],
+ 'timeout' => $starterTemplate['body']['timeout'],
+ 'commands' => $phpRuntime['commands'],
+ 'scopes' => $starterTemplate['body']['scopes'],
+ 'templateRepository' => $starterTemplate['body']['providerRepositoryId'],
+ 'templateOwner' => $starterTemplate['body']['providerOwner'],
+ 'templateRootDirectory' => $phpRuntime['providerRootDirectory'],
+ 'templateVersion' => $starterTemplate['body']['providerVersion'],
+ ]
+ );
$this->assertEquals(201, $function['headers']['status-code']);
$this->assertNotEmpty($function['body']['$id']);
- $functionId = $function['body']['$id'];
- // List deployments so we can await deployment build
- $deployments = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/deployments', [
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- 'x-appwrite-key' => $this->getProject()['apiKey'],
- ], []);
+ $functionId = $functionId = $function['body']['$id'] ?? '';
+
+ $deployments = $this->listDeployments($functionId);
$this->assertEquals(200, $deployments['headers']['status-code']);
$this->assertEquals(1, $deployments['body']['total']);
- $this->assertNotEmpty($deployments['body']['deployments'][0]['$id']);
- $this->assertEquals(0, $deployments['body']['deployments'][0]['size']);
- $deploymentId = $deployments['body']['deployments'][0]['$id'];
+ $lastDeployment = $deployments['body']['deployments'][0];
- // Wait for deployment build to finish
- // Deployment is automatically activated
- $this->awaitDeploymentIsBuilt($function['body']['$id'], $deploymentId);
+ $this->assertNotEmpty($lastDeployment['$id']);
+ $this->assertEquals(0, $lastDeployment['size']);
- $deployments = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/deployments/' . $deploymentId, [
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- 'x-appwrite-key' => $this->getProject()['apiKey'],
- ], []);
- $this->assertGreaterThan(0, $deployments['body']['size']);
+ $deploymentId = $lastDeployment['$id'];
- $function = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId, array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- 'x-appwrite-key' => $this->getProject()['apiKey'],
- ], $this->getHeaders()), []);
+ $this->assertEventually(function () use ($functionId, $deploymentId) {
+ $deployment = $this->getDeployment($functionId, $deploymentId);
+
+ $this->assertEquals(200, $deployment['headers']['status-code']);
+ $this->assertEquals('ready', $deployment['body']['status']);
+ }, 500000, 1000);
+
+ $function = $this->getFunction($functionId);
$this->assertEquals(200, $function['headers']['status-code']);
$this->assertEquals($deploymentId, $function['body']['deployment']);
- // Execute function to ensure starter code is used
- // Also tests if dynamic keys works as expected
- $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
- 'path' => '/ping'
+ // Test starter code is used and that dynamic keys work
+ $execution = $this->createExecution($functionId, [
+ 'path' => '/ping',
]);
$this->assertEquals(201, $execution['headers']['status-code']);
@@ -552,7 +393,7 @@ class FunctionsCustomServerTest extends Scope
$this->assertEquals("Pong", $execution['body']['responseBody']);
$this->assertEmpty($execution['body']['errors']);
- // Get users to ensure execution logged correct total users
+ // Test execution logged correct total users
$users = $this->client->call(Client::METHOD_GET, '/users', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
@@ -562,15 +403,12 @@ class FunctionsCustomServerTest extends Scope
$this->assertEquals(200, $users['headers']['status-code']);
$this->assertIsInt($users['body']['total']);
- $totalusers = $users['body']['total'];
+ $totalUsers = $users['body']['total'];
- $this->assertStringContainsString("Total users: " . $totalusers, $execution['body']['logs']);
+ $this->assertStringContainsString("Total users: " . $totalUsers, $execution['body']['logs']);
// Execute function again but async
- $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
+ $execution = $this->createExecution($functionId, [
'path' => '/ping',
'async' => true
]);
@@ -578,113 +416,93 @@ class FunctionsCustomServerTest extends Scope
$this->assertEquals(202, $execution['headers']['status-code']);
$this->assertNotEmpty($execution['body']['$id']);
$this->assertEquals('waiting', $execution['body']['status']);
- $executionId = $execution['body']['$id'];
- // Wait for async execuntion to finish
- sleep(5);
+ $executionId = $execution['body']['$id'] ?? '';
- // Ensure execution was successful
- $execution = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions/' . $executionId, array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), []);
+ $this->assertEventually(function () use ($functionId, $executionId, $totalUsers) {
+ $execution = $this->getExecution($functionId, $executionId);
- $this->assertEquals(200, $execution['headers']['status-code']);
- $this->assertEquals("completed", $execution['body']['status']);
- $this->assertEquals(200, $execution['body']['responseStatusCode']);
- $this->assertEmpty($execution['body']['responseBody']);
- $this->assertEmpty($execution['body']['errors']);
- $this->assertStringContainsString("Total users: " . $totalusers, $execution['body']['logs']);
+ $this->assertEquals(200, $execution['headers']['status-code']);
+ $this->assertEquals(200, $execution['body']['responseStatusCode']);
+ $this->assertEquals('completed', $execution['body']['status']);
+ $this->assertEmpty($execution['body']['responseBody']);
+ $this->assertEmpty($execution['body']['errors']);
+ $this->assertStringContainsString("Total users: " . $totalUsers, $execution['body']['logs']);
+ }, 10000, 500);
- // Cleanup : Delete function
- $response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, [
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- 'x-appwrite-key' => $this->getProject()['apiKey'],
- ], []);
-
- $this->assertEquals(204, $response['headers']['status-code']);
+ $function = $this->deleteFunction($functionId);
}
/**
- * @depends testUpdate
+ * @depends testUpdateFunction
*/
public function testCreateDeployment($data): array
{
/**
* Test for SUCCESS
*/
- $folder = 'php';
- $code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz";
- $this->packageCode($folder);
+ $functionId = $data['functionId'];
- $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $data['functionId'] . '/deployments', array_merge([
- 'content-type' => 'multipart/form-data',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
- 'code' => new CURLFile($code, 'application/x-gzip', \basename($code)),
+ $deployment = $this->createDeployment($functionId, [
+ 'code' => $this->packageFunction('php'),
'activate' => true
]);
- $deploymentId = $deployment['body']['$id'] ?? '';
-
$this->assertEquals(202, $deployment['headers']['status-code']);
$this->assertNotEmpty($deployment['body']['$id']);
$this->assertEquals(true, (new DatetimeValidator())->isValid($deployment['body']['$createdAt']));
$this->assertEquals('index.php', $deployment['body']['entrypoint']);
- $this->awaitDeploymentIsBuilt($data['functionId'], $deploymentId);
+ $deploymentIdActive = $deployment['body']['$id'] ?? '';
- $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $data['functionId'] . '/deployments', array_merge([
- 'content-type' => 'multipart/form-data',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
- 'code' => new CURLFile($code, 'application/x-gzip', \basename($code)),
+ $this->assertEventually(function () use ($functionId, $deploymentIdActive) {
+ $deployment = $this->getDeployment($functionId, $deploymentIdActive);
+
+ $this->assertEquals('ready', $deployment['body']['status']);
+ }, 50000, 500);
+
+ $deployment = $this->createDeployment($functionId, [
+ 'code' => $this->packageFunction('php'),
'activate' => 'false'
]);
$this->assertEquals(202, $deployment['headers']['status-code']);
$this->assertNotEmpty($deployment['body']['$id']);
- $deploymentIdInactive = $deployment['body']['$id'];
+ $deploymentIdInactive = $deployment['body']['$id'] ?? '';
- $this->awaitDeploymentIsBuilt($data['functionId'], $deploymentIdInactive);
+ $this->assertEventually(function () use ($functionId, $deploymentIdInactive) {
+ $deployment = $this->getDeployment($functionId, $deploymentIdInactive);
- $function = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'], array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()));
+ $this->assertEquals('ready', $deployment['body']['status']);
+ }, 50000, 500);
+
+ $function = $this->getFunction($functionId);
$this->assertEquals(200, $function['headers']['status-code']);
- $this->assertEquals($deploymentId, $function['body']['deployment']);
+ $this->assertEquals($deploymentIdActive, $function['body']['deployment']);
$this->assertNotEquals($deploymentIdInactive, $function['body']['deployment']);
- $deployment = $this->client->call(Client::METHOD_DELETE, '/functions/' . $data['functionId'] . '/deployments/' . $deploymentIdInactive, array_merge([
+ $deployment = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId . '/deployments/' . $deploymentIdInactive, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), []);
$this->assertEquals(204, $deployment['headers']['status-code']);
- return array_merge($data, ['deploymentId' => $deploymentId]);
+ return array_merge($data, ['deploymentId' => $deploymentIdActive]);
}
/**
- * @depends testUpdate
+ * @depends testUpdateFunction
*/
public function testCancelDeploymentBuild($data): void
{
- // Create a new deployment to cancel
- $folder = 'php';
- $code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz";
- $this->packageCode($folder);
+ $functionId = $data['functionId'];
- $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $data['functionId'] . '/deployments', array_merge([
- 'content-type' => 'multipart/form-data',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
- 'code' => new CURLFile($code, 'application/x-gzip', \basename($code)),
- 'activate' => true
+ $deployment = $this->createDeployment($functionId, [
+ 'code' => $this->packageFunction('php'),
+ 'activate' => 'false'
]);
$deploymentId = $deployment['body']['$id'] ?? '';
@@ -694,33 +512,18 @@ class FunctionsCustomServerTest extends Scope
$this->assertEquals(true, (new DatetimeValidator())->isValid($deployment['body']['$createdAt']));
$this->assertEquals('index.php', $deployment['body']['entrypoint']);
- // Poll until deployment is in progress
- while (true) {
- $deployment = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/deployments/' . $deploymentId, [
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- 'x-appwrite-key' => $this->getProject()['apiKey'],
- ]);
+ $this->assertEventually(function () use ($functionId, $deploymentId) {
+ $deployment = $this->getDeployment($functionId, $deploymentId);
- if (
- $deployment['headers']['status-code'] >= 400
- || $deployment['body']['status'] === 'building'
- ) {
- break;
- }
+ $this->assertEquals(200, $deployment['headers']['status-code']);
+ $this->assertEquals('building', $deployment['body']['status']);
+ }, 100000, 250);
- \sleep(1);
- }
-
- $this->assertEquals(200, $deployment['headers']['status-code']);
- $this->assertEquals('building', $deployment['body']['status']);
-
- // Cancel the deployment build
- $cancel = $this->client->call(Client::METHOD_PATCH, '/functions/' . $data['functionId'] . '/deployments/' . $deploymentId . '/build', [
+ // Cancel the deployment
+ $cancel = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployments/' . $deploymentId . '/build', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
- 'x-appwrite-key' => $this->getProject()['apiKey'],
- ]);
+ ], $this->getHeaders()));
$this->assertEquals(200, $cancel['headers']['status-code']);
$this->assertEquals('canceled', $cancel['body']['status']);
@@ -731,28 +534,26 @@ class FunctionsCustomServerTest extends Scope
* After build finished, it should still be canceled, not ready.
*/
\sleep(30);
- $deployment = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/deployments/' . $deploymentId, [
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- 'x-appwrite-key' => $this->getProject()['apiKey'],
- ]);
+
+ $deployment = $this->getDeployment($functionId, $deploymentId);
$this->assertEquals(200, $deployment['headers']['status-code']);
$this->assertEquals('canceled', $deployment['body']['status']);
}
/**
- * @depends testUpdate
+ * @depends testUpdateFunction
*/
public function testCreateDeploymentLarge($data): array
{
/**
* Test for Large Code File SUCCESS
*/
+ $functionId = $data['functionId'];
$folder = 'php-large';
$code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz";
- $this->packageCode($folder);
+ Console::execute('cd ' . realpath(__DIR__ . "/../../../resources/functions") . "/$folder && tar --exclude code.tar.gz -czf code.tar.gz .", '', $this->stdout, $this->stderr);
$chunkSize = 5 * 1024 * 1024;
$handle = @fopen($code, "rb");
@@ -770,7 +571,7 @@ class FunctionsCustomServerTest extends Scope
if (!empty($id)) {
$headers['x-appwrite-id'] = $id;
}
- $largeTag = $this->client->call(Client::METHOD_POST, '/functions/' . $data['functionId'] . '/deployments', array_merge($headers, $this->getHeaders()), [
+ $largeTag = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', array_merge($headers, $this->getHeaders()), [
'entrypoint' => 'index.php',
'code' => $curlFile,
'activate' => true,
@@ -789,19 +590,16 @@ class FunctionsCustomServerTest extends Scope
$this->assertLessThan(1024 * 1024 * 10, $largeTag['body']['size']); // ~7MB video file
$deploymentSize = $largeTag['body']['size'];
-
$deploymentId = $largeTag['body']['$id'];
- $this->awaitDeploymentIsBuilt($data['functionId'], $deploymentId, true);
+ $this->assertEventually(function () use ($functionId, $deploymentId, $deploymentSize) {
+ $deployment = $this->getDeployment($functionId, $deploymentId);
- $response = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/deployments/' . $deploymentId, array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), []);
-
- $this->assertEquals(200, $response['headers']['status-code']);
- $this->assertEquals($deploymentSize, $response['body']['size']);
- $this->assertGreaterThan(1024 * 1024 * 10, $response['body']['buildSize']); // ~7MB video file + 10MB sample file
+ $this->assertEquals(200, $deployment['headers']['status-code']);
+ $this->assertEquals('ready', $deployment['body']['status']);
+ $this->assertEquals($deploymentSize, $deployment['body']['size']);
+ $this->assertGreaterThan(1024 * 1024 * 10, $deployment['body']['buildSize']); // ~7MB video file + 10MB sample file
+ }, 500000, 1000);
return $data;
}
@@ -814,6 +612,8 @@ class FunctionsCustomServerTest extends Scope
/**
* Test for SUCCESS
*/
+ $dateValidator = new DatetimeValidator();
+
$response = $this->client->call(Client::METHOD_PATCH, '/functions/' . $data['functionId'] . '/deployments/' . $data['deploymentId'], array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
@@ -821,15 +621,10 @@ class FunctionsCustomServerTest extends Scope
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertNotEmpty($response['body']['$id']);
- $dateValidator = new DatetimeValidator();
$this->assertEquals(true, $dateValidator->isValid($response['body']['$createdAt']));
$this->assertEquals(true, $dateValidator->isValid($response['body']['$updatedAt']));
$this->assertEquals($data['deploymentId'], $response['body']['deployment']);
- /**
- * Test for FAILURE
- */
-
return $data;
}
@@ -841,127 +636,64 @@ class FunctionsCustomServerTest extends Scope
/**
* Test for SUCCESS
*/
- $function = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/deployments', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()));
+ $functionId = $data['functionId'];
+ $deployments = $this->listDeployments($functionId);
- $this->assertEquals($function['headers']['status-code'], 200);
- $this->assertEquals($function['body']['total'], 3);
- $this->assertIsArray($function['body']['deployments']);
- $this->assertCount(3, $function['body']['deployments']);
- $this->assertArrayHasKey('size', $function['body']['deployments'][0]);
- $this->assertArrayHasKey('buildSize', $function['body']['deployments'][0]);
+ $this->assertEquals($deployments['headers']['status-code'], 200);
+ $this->assertEquals($deployments['body']['total'], 3);
+ $this->assertIsArray($deployments['body']['deployments']);
+ $this->assertCount(3, $deployments['body']['deployments']);
+ $this->assertArrayHasKey('size', $deployments['body']['deployments'][0]);
+ $this->assertArrayHasKey('buildSize', $deployments['body']['deployments'][0]);
- /**
- * Test search queries
- */
- $function = $this->client->call(
- Client::METHOD_GET,
- '/functions/' . $data['functionId'] . '/deployments',
- array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders(), [
- 'search' => $data['functionId']
- ])
- );
-
- $this->assertEquals($function['headers']['status-code'], 200);
- $this->assertEquals(3, $function['body']['total']);
- $this->assertIsArray($function['body']['deployments']);
- $this->assertCount(3, $function['body']['deployments']);
- $this->assertEquals($function['body']['deployments'][0]['$id'], $data['deploymentId']);
-
- $function = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/deployments', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
+ $deployments = $this->listDeployments($functionId, [
'queries' => [
Query::limit(1)->toString(),
],
]);
- $this->assertEquals($function['headers']['status-code'], 200);
- $this->assertCount(1, $function['body']['deployments']);
+ $this->assertEquals($deployments['headers']['status-code'], 200);
+ $this->assertCount(1, $deployments['body']['deployments']);
- $function = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/deployments', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
+ $deployments = $this->listDeployments($functionId, [
'queries' => [
Query::offset(1)->toString(),
],
]);
- $this->assertEquals($function['headers']['status-code'], 200);
- $this->assertCount(2, $function['body']['deployments']);
+ $this->assertEquals($deployments['headers']['status-code'], 200);
+ $this->assertCount(2, $deployments['body']['deployments']);
- $function = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/deployments', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
+ $deployments = $this->listDeployments($functionId, [
'queries' => [
Query::equal('entrypoint', ['index.php'])->toString(),
],
]);
- $this->assertEquals($function['headers']['status-code'], 200);
- $this->assertCount(3, $function['body']['deployments']);
+ $this->assertEquals($deployments['headers']['status-code'], 200);
+ $this->assertCount(3, $deployments['body']['deployments']);
- $function = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/deployments', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
+ $deployments = $this->listDeployments($functionId, [
'queries' => [
Query::equal('entrypoint', ['index.js'])->toString(),
],
]);
- $this->assertEquals($function['headers']['status-code'], 200);
- $this->assertCount(0, $function['body']['deployments']);
+ $this->assertEquals($deployments['headers']['status-code'], 200);
+ $this->assertCount(0, $deployments['body']['deployments']);
- $function = $this->client->call(
- Client::METHOD_GET,
- '/functions/' . $data['functionId'] . '/deployments',
- array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders(), [
- 'search' => 'Test'
- ])
- );
+ $deployments = $this->listDeployments($functionId, [
+ 'search' => 'php-8.0'
+ ]);
- $this->assertEquals($function['headers']['status-code'], 200);
- $this->assertEquals(3, $function['body']['total']);
- $this->assertIsArray($function['body']['deployments']);
- $this->assertCount(3, $function['body']['deployments']);
- $this->assertEquals($function['body']['deployments'][0]['$id'], $data['deploymentId']);
+ $this->assertEquals($deployments['headers']['status-code'], 200);
+ $this->assertEquals(3, $deployments['body']['total']);
+ $this->assertIsArray($deployments['body']['deployments']);
+ $this->assertCount(3, $deployments['body']['deployments']);
+ $this->assertEquals($deployments['body']['deployments'][0]['$id'], $data['deploymentId']);
- $function = $this->client->call(
- Client::METHOD_GET,
- '/functions/' . $data['functionId'] . '/deployments',
- array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders(), [
- 'search' => 'php-8.0'
- ])
- );
-
- $this->assertEquals($function['headers']['status-code'], 200);
- $this->assertEquals(3, $function['body']['total']);
- $this->assertIsArray($function['body']['deployments']);
- $this->assertCount(3, $function['body']['deployments']);
- $this->assertEquals($function['body']['deployments'][0]['$id'], $data['deploymentId']);
-
- $function = $this->client->call(
- Client::METHOD_GET,
- '/functions/' . $data['functionId'] . '/deployments',
- array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()),
+ $deployments = $this->listDeployments(
+ $functionId,
[
'queries' => [
Query::equal('type', ['manual'])->toString(),
@@ -969,16 +701,11 @@ class FunctionsCustomServerTest extends Scope
]
);
- $this->assertEquals($function['headers']['status-code'], 200);
- $this->assertEquals(3, $function['body']['total']);
+ $this->assertEquals($deployments['headers']['status-code'], 200);
+ $this->assertEquals(3, $deployments['body']['total']);
- $function = $this->client->call(
- Client::METHOD_GET,
- '/functions/' . $data['functionId'] . '/deployments',
- array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()),
+ $deployments = $this->listDeployments(
+ $functionId,
[
'queries' => [
Query::equal('type', ['vcs'])->toString(),
@@ -986,16 +713,11 @@ class FunctionsCustomServerTest extends Scope
]
);
- $this->assertEquals($function['headers']['status-code'], 200);
- $this->assertEquals(0, $function['body']['total']);
+ $this->assertEquals($deployments['headers']['status-code'], 200);
+ $this->assertEquals(0, $deployments['body']['total']);
- $function = $this->client->call(
- Client::METHOD_GET,
- '/functions/' . $data['functionId'] . '/deployments',
- array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()),
+ $deployments = $this->listDeployments(
+ $functionId,
[
'queries' => [
Query::equal('type', ['invalid-string'])->toString(),
@@ -1003,16 +725,11 @@ class FunctionsCustomServerTest extends Scope
]
);
- $this->assertEquals($function['headers']['status-code'], 200);
- $this->assertEquals(0, $function['body']['total']);
+ $this->assertEquals($deployments['headers']['status-code'], 200);
+ $this->assertEquals(0, $deployments['body']['total']);
- $function = $this->client->call(
- Client::METHOD_GET,
- '/functions/' . $data['functionId'] . '/deployments',
- array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()),
+ $deployments = $this->listDeployments(
+ $functionId,
[
'queries' => [
Query::greaterThan('size', 10000)->toString(),
@@ -1020,16 +737,11 @@ class FunctionsCustomServerTest extends Scope
]
);
- $this->assertEquals($function['headers']['status-code'], 200);
- $this->assertEquals(1, $function['body']['total']);
+ $this->assertEquals($deployments['headers']['status-code'], 200);
+ $this->assertEquals(1, $deployments['body']['total']);
- $function = $this->client->call(
- Client::METHOD_GET,
- '/functions/' . $data['functionId'] . '/deployments',
- array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()),
+ $deployments = $this->listDeployments(
+ $functionId,
[
'queries' => [
Query::greaterThan('size', 0)->toString(),
@@ -1037,57 +749,41 @@ class FunctionsCustomServerTest extends Scope
]
);
- $this->assertEquals($function['headers']['status-code'], 200);
- $this->assertEquals(3, $function['body']['total']);
+ $this->assertEquals($deployments['headers']['status-code'], 200);
+ $this->assertEquals(3, $deployments['body']['total']);
- $function = $this->client->call(
- Client::METHOD_GET,
- '/functions/' . $data['functionId'] . '/deployments',
- array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()),
+ $deployments = $this->listDeployments(
+ $functionId,
[
'queries' => [
Query::greaterThan('size', -100)->toString(),
],
]
);
-
- $this->assertEquals($function['headers']['status-code'], 200);
- $this->assertEquals(3, $function['body']['total']);
+ $this->assertEquals($deployments['headers']['status-code'], 200);
+ $this->assertEquals(3, $deployments['body']['total']);
/**
* Ensure size output and size filters work exactly.
* Prevents buildSize being counted towards deployemtn size
*/
- $response = $this->client->call(
- Client::METHOD_GET,
- '/functions/' . $data['functionId'] . '/deployments',
- array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()),
+ $deployments = $this->listDeployments(
+ $functionId,
[
Query::limit(1)->toString(),
]
);
- $this->assertEquals(200, $response['headers']['status-code']);
- $this->assertGreaterThanOrEqual(1, $response['body']['total']);
- $this->assertNotEmpty($response['body']['deployments'][0]['$id']);
- $this->assertNotEmpty($response['body']['deployments'][0]['size']);
+ $this->assertEquals(200, $deployments['headers']['status-code']);
+ $this->assertGreaterThanOrEqual(1, $deployments['body']['total']);
+ $this->assertNotEmpty($deployments['body']['deployments'][0]['$id']);
+ $this->assertNotEmpty($deployments['body']['deployments'][0]['size']);
- $deploymentId = $function['body']['deployments'][0]['$id'];
- $deploymentSize = $function['body']['deployments'][0]['size'];
+ $deploymentId = $deployments['body']['deployments'][0]['$id'];
+ $deploymentSize = $deployments['body']['deployments'][0]['size'];
- $response = $this->client->call(
- Client::METHOD_GET,
- '/functions/' . $data['functionId'] . '/deployments',
- array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()),
+ $deployments = $this->listDeployments(
+ $functionId,
[
'queries' => [
Query::equal('size', [$deploymentSize])->toString(),
@@ -1095,20 +791,21 @@ class FunctionsCustomServerTest extends Scope
]
);
- $this->assertEquals(200, $response['headers']['status-code']);
- $this->assertGreaterThan(0, $response['body']['total']);
+ $this->assertEquals(200, $deployments['headers']['status-code']);
+ $this->assertGreaterThan(0, $deployments['body']['total']);
- $found = false;
- foreach ($response['body']['deployments'] as $deployment) {
- if ($deployment['$id'] === $deploymentId) {
- $found = true;
- $this->assertEquals($deploymentSize, $deployment['size']);
- break;
- }
+ $matchingDeployment = array_filter(
+ $deployments['body']['deployments'],
+ fn ($deployment) => $deployment['$id'] === $deploymentId
+ );
+
+ $this->assertNotEmpty($matchingDeployment, "Deployment with ID {$deploymentId} not found");
+
+ if (!empty($matchingDeployment)) {
+ $deployment = reset($matchingDeployment);
+ $this->assertEquals($deploymentSize, $deployment['size']);
}
- $this->assertTrue($found);
-
return $data;
}
@@ -1120,27 +817,21 @@ class FunctionsCustomServerTest extends Scope
/**
* Test for SUCCESS
*/
- $function = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/deployments/' . $data['deploymentId'], array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()));
+ $deployment = $this->getDeployment($data['functionId'], $data['deploymentId']);
- $this->assertEquals(200, $function['headers']['status-code']);
- $this->assertGreaterThan(0, $function['body']['buildTime']);
- $this->assertNotEmpty($function['body']['status']);
- $this->assertNotEmpty($function['body']['buildLogs']);
- $this->assertArrayHasKey('size', $function['body']);
- $this->assertArrayHasKey('buildSize', $function['body']);
+ $this->assertEquals(200, $deployment['headers']['status-code']);
+ $this->assertGreaterThan(0, $deployment['body']['buildTime']);
+ $this->assertNotEmpty($deployment['body']['status']);
+ $this->assertNotEmpty($deployment['body']['buildLogs']);
+ $this->assertArrayHasKey('size', $deployment['body']);
+ $this->assertArrayHasKey('buildSize', $deployment['body']);
/**
* Test for FAILURE
*/
- $function = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/deployments/x', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()));
+ $deployment = $this->getDeployment($data['functionId'], 'x');
- $this->assertEquals($function['headers']['status-code'], 404);
+ $this->assertEquals($deployment['headers']['status-code'], 404);
return $data;
}
@@ -1153,15 +844,10 @@ class FunctionsCustomServerTest extends Scope
/**
* Test for SUCCESS
*/
- $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $data['functionId'] . '/executions', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
- 'async' => false,
+ $execution = $this->createExecution($data['functionId'], [
+ 'async' => 'false',
]);
- $executionId = $execution['body']['$id'] ?? '';
-
$this->assertEquals(201, $execution['headers']['status-code']);
$this->assertNotEmpty($execution['body']['$id']);
$this->assertNotEmpty($execution['body']['functionId']);
@@ -1177,17 +863,17 @@ class FunctionsCustomServerTest extends Scope
$this->assertStringContainsString('8.0', $execution['body']['responseBody']);
$this->assertStringContainsString('Global Variable Value', $execution['body']['responseBody']);
// $this->assertStringContainsString('êä', $execution['body']['responseBody']); // tests unknown utf-8 chars
- $this->assertEquals('', $execution['body']['errors']);
- $this->assertEquals('', $execution['body']['logs']);
+ $this->assertNotEmpty($execution['body']['errors']);
+ $this->assertNotEmpty($execution['body']['logs']);
$this->assertLessThan(10, $execution['body']['duration']);
- $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $data['functionId'] . '/executions', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
- 'async' => false,
+ $executionId = $execution['body']['$id'] ?? '';
+
+ $execution = $this->createExecution($data['functionId'], [
+ 'async' => 'false',
'path' => '/?code=400'
]);
+
$this->assertEquals(201, $execution['headers']['status-code']);
$this->assertEquals('completed', $execution['body']['status']);
$this->assertEquals(400, $execution['body']['responseStatusCode']);
@@ -1196,6 +882,7 @@ class FunctionsCustomServerTest extends Scope
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), []);
+
$this->assertEquals(204, $execution['headers']['status-code']);
return array_merge($data, ['executionId' => $executionId]);
@@ -1209,82 +896,62 @@ class FunctionsCustomServerTest extends Scope
/**
* Test for SUCCESS
*/
- $function = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/executions', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()));
+ $executions = $this->listExecutions($data['functionId']);
- $this->assertEquals($function['headers']['status-code'], 200);
- $this->assertEquals($function['body']['total'], 1);
- $this->assertIsArray($function['body']['executions']);
- $this->assertCount(1, $function['body']['executions']);
- $this->assertEquals($function['body']['executions'][0]['$id'], $data['executionId']);
+ $this->assertEquals(200, $executions['headers']['status-code']);
+ $this->assertEquals(1, $executions['body']['total']);
+ $this->assertIsArray($executions['body']['executions']);
+ $this->assertCount(1, $executions['body']['executions']);
- $response = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/executions', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
+ $executions = $this->listExecutions($data['functionId'], [
'queries' => [
Query::limit(1)->toString(),
],
]);
- $this->assertEquals(200, $response['headers']['status-code']);
- $this->assertCount(1, $response['body']['executions']);
+ $this->assertEquals(200, $executions['headers']['status-code']);
+ $this->assertCount(1, $executions['body']['executions']);
- $response = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/executions', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
+ $executions = $this->listExecutions($data['functionId'], [
'queries' => [
- Query::offset(1)->toString(),
+ Query::offset(0)->toString(),
],
]);
- $this->assertEquals(200, $response['headers']['status-code']);
- $this->assertCount(0, $response['body']['executions']);
+ $this->assertEquals(200, $executions['headers']['status-code']);
+ $this->assertCount(1, $executions['body']['executions']);
- $response = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/executions', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
+ $executions = $this->listExecutions($data['functionId'], [
'queries' => [
Query::equal('trigger', ['http'])->toString(),
],
]);
- $this->assertEquals(200, $response['headers']['status-code']);
- $this->assertCount(1, $response['body']['executions']);
+ $this->assertEquals(200, $executions['headers']['status-code']);
+ $this->assertCount(1, $executions['body']['executions']);
/**
* Test search queries
*/
-
- $response = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/executions', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
+ $executions = $this->listExecutions($data['functionId'], [
'search' => $data['executionId'],
]);
- $this->assertEquals(200, $response['headers']['status-code']);
- $this->assertEquals(1, $response['body']['total']);
- $this->assertIsInt($response['body']['total']);
- $this->assertCount(1, $response['body']['executions']);
- $this->assertEquals($data['functionId'], $response['body']['executions'][0]['functionId']);
+ $this->assertEquals(200, $executions['headers']['status-code']);
+ $this->assertEquals(1, $executions['body']['total']);
+ $this->assertIsInt($executions['body']['total']);
+ $this->assertCount(1, $executions['body']['executions']);
+ $this->assertEquals($data['functionId'], $executions['body']['executions'][0]['functionId']);
- $response = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/executions', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
+ $executions = $this->listExecutions($data['functionId'], [
'search' => $data['functionId'],
]);
- $this->assertEquals(200, $response['headers']['status-code']);
- $this->assertEquals(1, $response['body']['total']);
- $this->assertIsInt($response['body']['total']);
- $this->assertCount(1, $response['body']['executions']);
- $this->assertEquals($data['executionId'], $response['body']['executions'][0]['$id']);
+ $this->assertEquals(200, $executions['headers']['status-code']);
+ $this->assertEquals(1, $executions['body']['total']);
+ $this->assertIsInt($executions['body']['total']);
+ $this->assertCount(1, $executions['body']['executions']);
+ $this->assertEquals($data['executionId'], $executions['body']['executions'][0]['$id']);
return $data;
}
@@ -1292,17 +959,13 @@ class FunctionsCustomServerTest extends Scope
/**
* @depends testUpdateDeployment
*/
- #[Retry(count: 2)]
public function testSyncCreateExecution($data): array
{
/**
* Test for SUCCESS
*/
- $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $data['functionId'] . '/executions', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
- // Testing default value, should be 'async' => false
+ $execution = $this->createExecution($data['functionId'], [
+ // Testing default value, should be 'async' => 'false'
]);
$this->assertEquals(201, $execution['headers']['status-code']);
@@ -1326,21 +989,15 @@ class FunctionsCustomServerTest extends Scope
/**
* Test for SUCCESS
*/
- $function = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/executions/' . $data['executionId'], array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()));
+ $execution = $this->getExecution($data['functionId'], $data['executionId']);
- $this->assertEquals($function['headers']['status-code'], 200);
- $this->assertEquals($function['body']['$id'], $data['executionId']);
+ $this->assertEquals($execution['headers']['status-code'], 200);
+ $this->assertEquals($execution['body']['$id'], $data['executionId']);
/**
* Test for FAILURE
*/
- $function = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/executions/x', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()));
+ $function = $this->getExecution($data['functionId'], 'x');
$this->assertEquals($function['headers']['status-code'], 404);
@@ -1383,6 +1040,7 @@ class FunctionsCustomServerTest extends Scope
]);
$executionId = $execution['body']['$id'] ?? '';
+
$this->assertEquals(202, $execution['headers']['status-code']);
$execution = $this->client->call(Client::METHOD_DELETE, '/functions/' . $data['functionId'] . '/executions/' . $executionId, array_merge([
@@ -1397,46 +1055,18 @@ class FunctionsCustomServerTest extends Scope
return $data;
}
- /**
- * @depends testGetExecution
- */
- public function testDeleteScheduledExecution($data): array
- {
- $futureTime = (new \DateTime())->add(new \DateInterval('PT10H'));
- $futureTime->setTime($futureTime->format('H'), $futureTime->format('i'), 0, 0);
- $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $data['functionId'] . '/executions', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
- 'async' => true,
- 'scheduledAt' => $futureTime->format('Y-m-d H:i:s'),
- ]);
-
- $executionId = $execution['body']['$id'] ?? '';
- $this->assertEquals(202, $execution['headers']['status-code']);
- sleep(5);
- $execution = $this->client->call(Client::METHOD_DELETE, '/functions/' . $data['functionId'] . '/executions/' . $executionId, array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()));
-
- $this->assertEquals(204, $execution['headers']['status-code']);
- $this->assertEmpty($execution['body']);
-
- return $data;
- }
/**
* @depends testGetExecution
*/
- #[Retry(count: 2)]
public function testUpdateSpecs($data): array
{
/**
* Test for SUCCESS
*/
- $response1 = $this->client->call(Client::METHOD_PUT, '/functions/' . $data['functionId'], array_merge([
+ // Change the function specs
+ $function = $this->client->call(Client::METHOD_PUT, '/functions/' . $data['functionId'], array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
@@ -1451,22 +1081,20 @@ class FunctionsCustomServerTest extends Scope
'specification' => Specification::S_1VCPU_1GB,
]);
- $this->assertEquals(200, $response1['headers']['status-code']);
- $this->assertNotEmpty($response1['body']['$id']);
- $this->assertEquals(Specification::S_1VCPU_1GB, $response1['body']['specification']);
+ $this->assertEquals(200, $function['headers']['status-code']);
+ $this->assertNotEmpty($function['body']['$id']);
+ $this->assertEquals(Specification::S_1VCPU_1GB, $function['body']['specification']);
- // Test Execution
- $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $data['functionId'] . '/executions', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()));
+ // Verify the updated specs
+ $execution = $this->createExecution($data['functionId']);
$output = json_decode($execution['body']['responseBody'], true);
$this->assertEquals(1, $output['APPWRITE_FUNCTION_CPUS']);
$this->assertEquals(1024, $output['APPWRITE_FUNCTION_MEMORY']);
- $response2 = $this->client->call(Client::METHOD_PUT, '/functions/' . $data['functionId'], array_merge([
+ // Change the specs to 1vcpu 512mb
+ $function = $this->client->call(Client::METHOD_PUT, '/functions/' . $data['functionId'], array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
@@ -1481,15 +1109,12 @@ class FunctionsCustomServerTest extends Scope
'specification' => Specification::S_1VCPU_512MB,
]);
- $this->assertEquals(200, $response2['headers']['status-code']);
- $this->assertNotEmpty($response2['body']['$id']);
- $this->assertEquals(Specification::S_1VCPU_512MB, $response2['body']['specification']);
+ $this->assertEquals(200, $function['headers']['status-code']);
+ $this->assertNotEmpty($function['body']['$id']);
+ $this->assertEquals(Specification::S_1VCPU_512MB, $function['body']['specification']);
- // Test Execution
- $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $data['functionId'] . '/executions', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()));
+ // Verify the updated specs
+ $execution = $this->createExecution($data['functionId']);
$output = json_decode($execution['body']['responseBody'], true);
@@ -1499,7 +1124,7 @@ class FunctionsCustomServerTest extends Scope
/**
* Test for FAILURE
*/
- $response3 = $this->client->call(Client::METHOD_PUT, '/functions/' . $data['functionId'], array_merge([
+ $function = $this->client->call(Client::METHOD_PUT, '/functions/' . $data['functionId'], array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
@@ -1514,8 +1139,8 @@ class FunctionsCustomServerTest extends Scope
'specification' => 's-2vcpu-512mb', // Invalid specification
]);
- $this->assertEquals(400, $response3['headers']['status-code']);
- $this->assertStringStartsWith('Invalid `specification` param: Specification must be one of:', $response3['body']['message']);
+ $this->assertEquals(400, $function['headers']['status-code']);
+ $this->assertStringStartsWith('Invalid `specification` param: Specification must be one of:', $function['body']['message']);
return $data;
}
@@ -1528,24 +1153,17 @@ class FunctionsCustomServerTest extends Scope
/**
* Test for SUCCESS
*/
- $function = $this->client->call(Client::METHOD_DELETE, '/functions/' . $data['functionId'] . '/deployments/' . $data['deploymentId'], array_merge([
+ $deployment = $this->client->call(Client::METHOD_DELETE, '/functions/' . $data['functionId'] . '/deployments/' . $data['deploymentId'], array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
- $this->assertEquals(204, $function['headers']['status-code']);
- $this->assertEmpty($function['body']);
+ $this->assertEquals(204, $deployment['headers']['status-code']);
+ $this->assertEmpty($deployment['body']);
- $function = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/deployments/' . $data['deploymentId'], array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()));
+ $deployment = $this->getDeployment($data['functionId'], $data['deploymentId']);
- $this->assertEquals(404, $function['headers']['status-code']);
-
- /**
- * Test for FAILURE
- */
+ $this->assertEquals(404, $deployment['headers']['status-code']);
return $data;
}
@@ -1553,153 +1171,63 @@ class FunctionsCustomServerTest extends Scope
/**
* @depends testCreateDeployment
*/
- public function testDelete($data): array
+ public function testDeleteFunction($data): array
{
/**
* Test for SUCCESS
*/
- $function = $this->client->call(Client::METHOD_DELETE, '/functions/' . $data['functionId'], array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()));
+ $function = $this->deleteFunction($data['functionId']);
$this->assertEquals(204, $function['headers']['status-code']);
$this->assertEmpty($function['body']);
- $function = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'], array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()));
+ $function = $this->getFunction($data['functionId']);
$this->assertEquals(404, $function['headers']['status-code']);
- /**
- * Test for FAILURE
- */
-
return $data;
}
- public function testTimeout()
+ public function testExecutionTimeout()
{
- $name = 'php-8.0';
- $entrypoint = 'index.php';
- $timeout = 15;
- $folder = 'timeout';
- $code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz";
- $this->packageCode($folder);
-
- $function = $this->client->call(Client::METHOD_POST, '/functions', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
+ $functionId = $this->setupFunction([
'functionId' => ID::unique(),
- 'name' => 'Test ' . $name,
- 'runtime' => $name,
- 'entrypoint' => $entrypoint,
+ 'name' => 'Test php-8.0',
+ 'runtime' => 'php-8.0',
+ 'entrypoint' => 'index.php',
'events' => [],
- 'schedule' => '* * * * *', // execute every minute
- 'timeout' => $timeout,
+ 'schedule' => '',
+ 'timeout' => 5, // Should timeout after 5 seconds
]);
-
- $functionId = $function['body']['$id'] ?? '';
-
- $this->assertEquals(201, $function['headers']['status-code']);
- $this->assertEquals('* * * * *', $function['body']['schedule']);
-
- $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', array_merge([
- 'content-type' => 'multipart/form-data',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
- 'entrypoint' => $entrypoint,
- 'code' => new CURLFile($code, 'application/x-gzip', basename($code)),
+ $this->setupDeployment($functionId, [
+ 'code' => $this->packageFunction('timeout'),
'activate' => true,
]);
- $deploymentId = $deployment['body']['$id'] ?? '';
-
- $this->assertEquals(202, $deployment['headers']['status-code']);
-
- $this->awaitDeploymentIsBuilt($functionId, $deploymentId);
-
- $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
- 'async' => true,
+ $execution = $this->createExecution($functionId, [
+ 'async' => true
]);
- $executionId = $execution['body']['$id'] ?? '';
-
$this->assertEquals(202, $execution['headers']['status-code']);
- sleep(20);
+ $executionId = $execution['body']['$id'] ?? '';
- $executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
- 'queries' => [
- Query::equal('trigger', ['http'])->toString(),
- ],
- ]);
+ \sleep(5); // Wait for the function to timeout
- $this->assertEquals($executions['headers']['status-code'], 200);
- $this->assertEquals($executions['body']['total'], 1);
- $this->assertIsArray($executions['body']['executions']);
- $this->assertCount(1, $executions['body']['executions']);
- $this->assertEquals($executions['body']['executions'][0]['$id'], $executionId);
- $this->assertEquals($executions['body']['executions'][0]['trigger'], 'http');
- $this->assertEquals($executions['body']['executions'][0]['status'], 'failed');
- $this->assertEquals($executions['body']['executions'][0]['responseStatusCode'], 500);
- $this->assertGreaterThan(2, $executions['body']['executions'][0]['duration']);
- $this->assertLessThan(20, $executions['body']['executions'][0]['duration']);
- $this->assertEquals($executions['body']['executions'][0]['responseBody'], '');
- $this->assertEquals($executions['body']['executions'][0]['logs'], '');
- $this->assertStringContainsString('timed out', $executions['body']['executions'][0]['errors']);
+ $this->assertEventually(function () use ($functionId, $executionId) {
+ $execution = $this->getExecution($functionId, $executionId);
- $start = \microtime(true);
- while (true) {
- $executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
- 'queries' => [
- Query::equal('trigger', ['schedule'])->toString(),
- ],
- ]);
+ $this->assertEquals(200, $execution['headers']['status-code']);
+ $this->assertEquals('failed', $execution['body']['status']);
+ $this->assertEquals(500, $execution['body']['responseStatusCode']);
+ $this->assertGreaterThan(2, $execution['body']['duration']);
+ $this->assertLessThan(20, $execution['body']['duration']);
+ $this->assertEquals('', $execution['body']['responseBody']);
+ $this->assertEquals('', $execution['body']['logs']);
+ $this->assertStringContainsString('timed out', $execution['body']['errors']);
+ }, 10000, 500);
- $this->assertEquals(200, $executions['headers']['status-code']);
-
- if (\count($executions['body']['executions']) > 0) {
- break;
- }
-
- // 0s would mean instant execution
- // +60 seconds, maximum possible waiting time before next minute
- // +10 seconds, maximum update interval time
- // +60 seconds, possible overlap between update and schedule tick
- // +10 seconds, maximum execution time including cold-start
- // Result: We allow maximum
- if (\microtime(true) - $start > 140) {
- $this->fail('Execution did not create within 140 seconds of schedule: ' . \json_encode($executions));
- }
-
- usleep(1000000); // 1 second
- }
-
- $this->assertEquals(200, $executions['headers']['status-code']);
- $this->assertGreaterThanOrEqual(1, \count($executions['body']['executions']));
- $this->assertEquals($executions['body']['executions'][0]['trigger'], 'schedule');
-
- // Cleanup : Delete function
- $response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, [
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- 'x-appwrite-key' => $this->getProject()['apiKey'],
- ], []);
-
- $this->assertEquals(204, $response['headers']['status-code']);
+ $this->cleanupFunction($functionId);
}
/**
@@ -1725,72 +1253,38 @@ class FunctionsCustomServerTest extends Scope
* @param string $entrypoint
*
* @dataProvider provideCustomExecutions
- * @depends testTimeout
+ * @depends testExecutionTimeout
*/
public function testCreateCustomExecution(string $folder, string $name, string $entrypoint, string $runtimeName, string $runtimeVersion)
{
- $timeout = 15;
- $code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz";
- $this->packageCode($folder);
-
- $function = $this->client->call(Client::METHOD_POST, '/functions', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
+ $functionId = $this->setupFunction([
'functionId' => ID::unique(),
'name' => 'Test ' . $name,
'runtime' => $name,
'entrypoint' => $entrypoint,
'events' => [],
- 'timeout' => $timeout,
+ 'timeout' => 15,
]);
- $functionId = $function['body']['$id'] ?? '';
-
- $this->assertEquals(201, $function['headers']['status-code']);
-
- $variable = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/variables', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
+ $variable = $this->createVariable($functionId, [
'key' => 'CUSTOM_VARIABLE',
- 'value' => 'variable',
+ 'value' => 'variable'
]);
$this->assertEquals(201, $variable['headers']['status-code']);
- $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', array_merge([
- 'content-type' => 'multipart/form-data',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
+ $deploymentId = $this->setupDeployment($functionId, [
'entrypoint' => $entrypoint,
- 'code' => new CURLFile($code, 'application/x-gzip', basename($code)),
+ 'code' => $this->packageFunction($folder),
'activate' => true
]);
- $deploymentId = $deployment['body']['$id'] ?? '';
- $this->assertEquals(202, $deployment['headers']['status-code']);
-
- $this->awaitDeploymentIsBuilt($function['body']['$id'], $deploymentId, checkForSuccess: false);
-
- $deployment = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployments/' . $deploymentId, array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), []);
-
- $this->assertEquals(200, $deployment['headers']['status-code']);
-
- $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
+ $execution = $this->createExecution($functionId, [
'body' => 'foobar',
- 'async' => false
+ 'async' => 'false'
]);
- $executionId = $execution['body']['$id'] ?? '';
$output = json_decode($execution['body']['responseBody'], true);
-
$this->assertEquals(201, $execution['headers']['status-code']);
$this->assertEquals('completed', $execution['body']['status']);
$this->assertEquals(200, $execution['body']['responseStatusCode']);
@@ -1809,10 +1303,9 @@ class FunctionsCustomServerTest extends Scope
$this->assertStringContainsString('Amazing Function Log', $execution['body']['logs']);
$this->assertEmpty($execution['body']['errors']);
- $executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()));
+ $executionId = $execution['body']['$id'] ?? '';
+
+ $executions = $this->listExecutions($functionId);
$this->assertEquals($executions['headers']['status-code'], 200);
$this->assertEquals($executions['body']['total'], 1);
@@ -1822,65 +1315,28 @@ class FunctionsCustomServerTest extends Scope
$this->assertEquals($executions['body']['executions'][0]['trigger'], 'http');
$this->assertStringContainsString('Amazing Function Log', $executions['body']['executions'][0]['logs']);
- // Cleanup : Delete function
- $response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, [
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- 'x-appwrite-key' => $this->getProject()['apiKey'],
- ], []);
-
- $this->assertEquals(204, $response['headers']['status-code']);
+ $this->cleanupFunction($functionId);
}
public function testCreateCustomExecutionBinaryResponse()
{
- $timeout = 15;
- $code = realpath(__DIR__ . '/../../../resources/functions') . "/php-binary-response/code.tar.gz";
- $this->packageCode('php-binary-response');
-
- $function = $this->client->call(Client::METHOD_POST, '/functions', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
+ $functionId = $this->setupFunction([
'functionId' => ID::unique(),
'name' => 'Test PHP Binary executions',
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
- 'timeout' => $timeout,
+ 'timeout' => 15,
'execute' => ['any']
]);
-
- $functionId = $function['body']['$id'] ?? '';
-
- $this->assertEquals(201, $function['headers']['status-code']);
-
- $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', array_merge([
- 'content-type' => 'multipart/form-data',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
+ $this->setupDeployment($functionId, [
'entrypoint' => 'index.php',
- 'code' => new CURLFile($code, 'application/x-gzip', basename($code)),
+ 'code' => $this->packageFunction('php-binary-response'),
'activate' => true
]);
- $deploymentId = $deployment['body']['$id'] ?? '';
- $this->assertEquals(202, $deployment['headers']['status-code']);
-
- $this->awaitDeploymentIsBuilt($function['body']['$id'], $deploymentId, checkForSuccess: false);
-
- $deployment = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployments/' . $deploymentId, array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), []);
-
- $this->assertEquals(200, $deployment['headers']['status-code']);
-
- // Wait a little for activation to finish
- sleep(5);
-
$execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([
'x-appwrite-project' => $this->getProject()['$id'],
- 'accept' => 'multipart/form-data',
+ 'accept' => 'multipart/form-data', // Accept binary response
], $this->getHeaders()), [
'body' => null,
]);
@@ -1900,7 +1356,7 @@ class FunctionsCustomServerTest extends Scope
*/
$execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([
'x-appwrite-project' => $this->getProject()['$id'],
- 'accept' => 'application/json',
+ 'accept' => 'application/json', // Accept JSON response
], $this->getHeaders()), [
'body' => null,
]);
@@ -1908,66 +1364,29 @@ class FunctionsCustomServerTest extends Scope
$this->assertEquals(400, $execution['headers']['status-code']);
$this->assertStringContainsString('Failed to parse response', $execution['body']['message']);
- // Cleanup : Delete function
- $response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, [
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- 'x-appwrite-key' => $this->getProject()['apiKey'],
- ], []);
-
- $this->assertEquals(204, $response['headers']['status-code']);
+ $this->cleanupFunction($functionId);
}
public function testCreateCustomExecutionBinaryRequest()
{
- $timeout = 15;
- $code = realpath(__DIR__ . '/../../../resources/functions') . "/php-binary-request/code.tar.gz";
- $this->packageCode('php-binary-request');
-
- $function = $this->client->call(Client::METHOD_POST, '/functions', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
+ $functionId = $this->setupFunction([
'functionId' => ID::unique(),
'name' => 'Test PHP Binary executions',
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
- 'timeout' => $timeout,
+ 'timeout' => 15,
'execute' => ['any']
]);
-
- $functionId = $function['body']['$id'] ?? '';
-
- $this->assertEquals(201, $function['headers']['status-code']);
-
- $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', array_merge([
- 'content-type' => 'multipart/form-data',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
+ $this->setupDeployment($functionId, [
'entrypoint' => 'index.php',
- 'code' => new CURLFile($code, 'application/x-gzip', basename($code)),
+ 'code' => $this->packageFunction('php-binary-request'),
'activate' => true
]);
- $deploymentId = $deployment['body']['$id'] ?? '';
- $this->assertEquals(202, $deployment['headers']['status-code']);
-
- $this->awaitDeploymentIsBuilt($function['body']['$id'], $deploymentId, checkForSuccess: false);
-
- $deployment = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployments/' . $deploymentId, array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), []);
-
- $this->assertEquals(200, $deployment['headers']['status-code']);
-
- // Wait a little for activation to finish
- sleep(5);
-
$bytes = pack('C*', ...[0, 20, 255]);
$execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([
- 'content-type' => 'multipart/form-data',
+ 'content-type' => 'multipart/form-data', // Send binary request
'x-appwrite-project' => $this->getProject()['$id'],
'accept' => 'application/json',
], $this->getHeaders()), [
@@ -1984,7 +1403,7 @@ class FunctionsCustomServerTest extends Scope
* Test for FAILURE
*/
$execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([
- 'content-type' => 'application/json',
+ 'content-type' => 'application/json', // Send JSON headers
'x-appwrite-project' => $this->getProject()['$id'],
'accept' => 'application/json',
], $this->getHeaders()), [
@@ -1992,98 +1411,53 @@ class FunctionsCustomServerTest extends Scope
], false);
$executionBody = json_decode($execution['body'], true);
+
$this->assertNotEquals(\md5($bytes), $executionBody['responseBody']);
- // Cleanup : Delete function
- $response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, [
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- 'x-appwrite-key' => $this->getProject()['apiKey'],
- ], []);
-
- $this->assertEquals(204, $response['headers']['status-code']);
+ $this->cleanupFunction($functionId);
}
public function testv2Function()
{
- $timeout = 15;
- $code = realpath(__DIR__ . '/../../../resources/functions') . "/php-v2/code.tar.gz";
- $this->packageCode('php-v2');
-
- $function = $this->client->call(Client::METHOD_POST, '/functions', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
+ $functionId = $this->setupFunction([
'functionId' => ID::unique(),
'name' => 'Test PHP V2',
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'events' => [],
- 'timeout' => $timeout,
+ 'timeout' => 15,
]);
- $functionId = $function['body']['$id'] ?? '';
-
- $this->assertEquals(201, $function['headers']['status-code']);
-
- $headers = [
+ $variable = $this->client->call(Client::METHOD_PATCH, '/mock/functions-v2', [
'content-type' => 'application/json',
'origin' => 'http://localhost',
'cookie' => 'a_session_console=' . $this->getRoot()['session'],
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-mode' => 'admin',
- ];
-
- $variable = $this->client->call(Client::METHOD_PATCH, '/mock/functions-v2', $headers, [
+ ], [
'functionId' => $functionId
]);
-
$this->assertEquals(204, $variable['headers']['status-code']);
- $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', array_merge([
- 'content-type' => 'multipart/form-data',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
+ $this->setupDeployment($functionId, [
'entrypoint' => 'index.php',
- 'code' => new CURLFile($code, 'application/x-gzip', basename($code)),
+ 'code' => $this->packageFunction('php-v2'),
'activate' => true
]);
- $deploymentId = $deployment['body']['$id'] ?? '';
- $this->assertEquals(202, $deployment['headers']['status-code']);
-
- $this->awaitDeploymentIsBuilt($function['body']['$id'], $deploymentId, checkForSuccess: false);
-
- $deployment = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployments/' . $deploymentId, array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), []);
-
- $this->assertEquals(200, $deployment['headers']['status-code']);
-
- $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
+ $execution = $this->createExecution($functionId, [
'body' => 'foobar',
- 'async' => false
+ 'async' => 'false'
]);
- $output = json_decode($execution['body']['responseBody'], true);
-
$this->assertEquals(201, $execution['headers']['status-code']);
$this->assertEquals('completed', $execution['body']['status']);
$this->assertEquals(200, $execution['body']['responseStatusCode']);
+
+ $output = json_decode($execution['body']['responseBody'], true);
$this->assertEquals(true, $output['v2Woks']);
- // Cleanup : Delete function
- $response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, [
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- 'x-appwrite-key' => $this->getProject()['apiKey'],
- ], []);
-
- $this->assertEquals(204, $response['headers']['status-code']);
+ $this->cleanupFunction($functionId);
}
public function testGetRuntimes()
@@ -2111,14 +1485,7 @@ class FunctionsCustomServerTest extends Scope
public function testEventTrigger()
{
- $timeout = 15;
- $code = realpath(__DIR__ . '/../../../resources/functions') . "/php-event/code.tar.gz";
- $this->packageCode('php-event');
-
- $function = $this->client->call(Client::METHOD_POST, '/functions', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
+ $functionId = $this->setupFunction([
'functionId' => ID::unique(),
'name' => 'Test PHP Event executions',
'runtime' => 'php-8.0',
@@ -2126,38 +1493,15 @@ class FunctionsCustomServerTest extends Scope
'events' => [
'users.*.create',
],
- 'timeout' => $timeout,
+ 'timeout' => 15,
]);
-
- $functionId = $function['body']['$id'] ?? '';
-
- $this->assertEquals(201, $function['headers']['status-code']);
-
- $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', array_merge([
- 'content-type' => 'multipart/form-data',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
+ $this->setupDeployment($functionId, [
'entrypoint' => 'index.php',
- 'code' => new CURLFile($code, 'application/x-gzip', basename($code)),
+ 'code' => $this->packageFunction('php-event'),
'activate' => true
]);
- $deploymentId = $deployment['body']['$id'] ?? '';
- $this->assertEquals(202, $deployment['headers']['status-code']);
-
- $this->awaitDeploymentIsBuilt($function['body']['$id'], $deploymentId, checkForSuccess: false);
-
- $deployment = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployments/' . $deploymentId, array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), []);
-
- $this->assertEquals(200, $deployment['headers']['status-code']);
-
- // Wait a little for activation to finish
- sleep(5);
-
- // Create user to trigger event
+ // Create user as an event trigger
$user = $this->client->call(Client::METHOD_POST, '/users', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
@@ -2166,108 +1510,59 @@ class FunctionsCustomServerTest extends Scope
'name' => 'Event User'
]);
- $userId = $user['body']['$id'];
-
$this->assertEquals(201, $user['headers']['status-code']);
- // Wait for execution to occur
- sleep(15);
+ $userId = $user['body']['$id'] ?? '';
- $executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()));
+ $this->assertEventually(function () use ($functionId, $userId) {
+ $executions = $this->listExecutions($functionId);
- $execution = $executions['body']['executions'][0];
+ $lastExecution = $executions['body']['executions'][0];
- $this->assertEquals(200, $executions['headers']['status-code']);
- $this->assertEquals('completed', $execution['status']);
- $this->assertEquals(204, $execution['responseStatusCode']);
- $this->assertStringContainsString($userId, $execution['logs']);
- $this->assertStringContainsString('Event User', $execution['logs']);
+ $this->assertEquals('completed', $lastExecution['status']);
+ $this->assertEquals(204, $lastExecution['responseStatusCode']);
+ $this->assertStringContainsString($userId, $lastExecution['logs']);
+ $this->assertStringContainsString('Event User', $lastExecution['logs']);
+ }, 10000, 500);
- // Cleanup : Delete function
- $response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, [
+ $this->cleanupFunction($functionId);
+
+ // Cleanup user
+ $user = $this->client->call(Client::METHOD_DELETE, '/users/' . $userId, [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
], []);
-
- $this->assertEquals(204, $response['headers']['status-code']);
-
- // Cleanup : Delete user
- $response = $this->client->call(Client::METHOD_DELETE, '/users/' . $userId, [
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- 'x-appwrite-key' => $this->getProject()['apiKey'],
- ], []);
-
- $this->assertEquals(204, $response['headers']['status-code']);
+ $this->assertEquals(204, $user['headers']['status-code']);
}
public function testScopes()
{
- $timeout = 15;
- $code = realpath(__DIR__ . '/../../../resources/functions') . "/php-scopes/code.tar.gz";
- $this->packageCode('php-scopes');
-
- $function = $this->client->call(Client::METHOD_POST, '/functions', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
+ $functionId = $this->setupFunction([
'functionId' => ID::unique(),
'name' => 'Test PHP Scopes executions',
- 'commands' => 'composer update --no-interaction --ignore-platform-reqs --optimize-autoloader --prefer-dist --no-dev',
+ 'commands' => 'sh setup.sh && composer install',
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'scopes' => ['users.read'],
- 'timeout' => $timeout,
+ 'timeout' => 15,
]);
- $functionId = $function['body']['$id'] ?? '';
-
- $this->assertEquals(201, $function['headers']['status-code']);
-
- $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', array_merge([
- 'content-type' => 'multipart/form-data',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
+ $deploymentId = $this->setupDeployment($functionId, [
'entrypoint' => 'index.php',
- 'commands' => 'sh setup.sh && composer install',
- 'code' => new CURLFile($code, 'application/x-gzip', basename($code)),
- 'activate' => true
+ 'code' => $this->packageFunction('php-scopes'),
+ 'activate' => true,
]);
- $deploymentId = $deployment['body']['$id'] ?? '';
- $this->assertEquals(202, $deployment['headers']['status-code']);
-
- $this->awaitDeploymentIsBuilt($function['body']['$id'], $deploymentId, checkForSuccess: false);
-
- $deployment = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/deployments/' . $deploymentId, array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), []);
+ $deployment = $this->getDeployment($functionId, $deploymentId);
$this->assertEquals(200, $deployment['headers']['status-code']);
$this->assertStringContainsStringIgnoringCase("200 OK", $deployment['body']['buildLogs']);
$this->assertStringContainsStringIgnoringCase('"total":', $deployment['body']['buildLogs']);
$this->assertStringContainsStringIgnoringCase('"users":', $deployment['body']['buildLogs']);
- $deployment = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployments/' . $deploymentId, array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), []);
-
- $this->assertEquals(200, $deployment['headers']['status-code']);
-
- // Wait a little for activation to finish
- sleep(5);
-
- $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
- 'async' => false
+ $execution = $this->createExecution($functionId, [
+ 'async' => 'false',
]);
$this->assertEquals(201, $execution['headers']['status-code']);
@@ -2277,92 +1572,47 @@ class FunctionsCustomServerTest extends Scope
$this->assertNotEmpty($execution['body']['responseBody']);
$this->assertStringContainsString("total", $execution['body']['responseBody']);
- $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
- 'async' => true
+ $execution = $this->createExecution($functionId, [
+ 'async' => true,
]);
$this->assertEquals(202, $execution['headers']['status-code']);
$this->assertNotEmpty($execution['body']['$id']);
- \sleep(10);
+ $executionId = $execution['body']['$id'] ?? '';
- $execution = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions/' . $execution['body']['$id'], array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), []);
+ $this->assertEventually(function () use ($functionId, $executionId) {
+ $execution = $this->getExecution($functionId, $executionId);
- $this->assertEquals(200, $execution['headers']['status-code']);
- $this->assertEquals('completed', $execution['body']['status']);
- $this->assertEquals(200, $execution['body']['responseStatusCode']);
- $this->assertGreaterThan(0, $execution['body']['duration']);
- $this->assertNotEmpty($execution['body']['logs']);
- $this->assertStringContainsString("total", $execution['body']['logs']);
+ $this->assertEquals(200, $execution['headers']['status-code']);
+ $this->assertEquals('completed', $execution['body']['status']);
+ $this->assertEquals(200, $execution['body']['responseStatusCode']);
+ $this->assertGreaterThan(0, $execution['body']['duration']);
+ $this->assertNotEmpty($execution['body']['logs']);
+ $this->assertStringContainsString("total", $execution['body']['logs']);
+ }, 10000, 500);
- // Cleanup : Delete function
- $response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, [
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- 'x-appwrite-key' => $this->getProject()['apiKey'],
- ], []);
-
- $this->assertEquals(204, $response['headers']['status-code']);
+ $this->cleanupFunction($functionId);
}
public function testCookieExecution()
{
- $timeout = 15;
- $code = realpath(__DIR__ . '/../../../resources/functions') . "/php-cookie/code.tar.gz";
- $this->packageCode('php-cookie');
-
- $function = $this->client->call(Client::METHOD_POST, '/functions', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
+ $functionId = $this->setupFunction([
'functionId' => ID::unique(),
'name' => 'Test PHP Cookie executions',
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
- 'timeout' => $timeout,
+ 'timeout' => 15,
]);
-
- $functionId = $function['body']['$id'] ?? '';
-
- $this->assertEquals(201, $function['headers']['status-code']);
-
- $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', array_merge([
- 'content-type' => 'multipart/form-data',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
+ $this->setupDeployment($functionId, [
'entrypoint' => 'index.php',
- 'code' => new CURLFile($code, 'application/x-gzip', basename($code)),
+ 'code' => $this->packageFunction('php-cookie'),
'activate' => true
]);
- $deploymentId = $deployment['body']['$id'] ?? '';
- $this->assertEquals(202, $deployment['headers']['status-code']);
-
- $this->awaitDeploymentIsBuilt($function['body']['$id'], $deploymentId, checkForSuccess: false);
-
- $deployment = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployments/' . $deploymentId, array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), []);
-
- $this->assertEquals(200, $deployment['headers']['status-code']);
-
- // Wait a little for activation to finish
- sleep(5);
-
$cookie = 'cookieName=cookieValue; cookie2=value2; cookie3=value=3; cookie4=val:ue4; cookie5=value5';
-
- $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
- 'async' => false,
+ $execution = $this->createExecution($functionId, [
+ 'async' => 'false',
'headers' => [
'cookie' => $cookie
]
@@ -2374,38 +1624,20 @@ class FunctionsCustomServerTest extends Scope
$this->assertEquals($cookie, $execution['body']['responseBody']);
$this->assertGreaterThan(0, $execution['body']['duration']);
- // Cleanup : Delete function
- $response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, [
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- 'x-appwrite-key' => $this->getProject()['apiKey'],
- ], []);
-
- $this->assertEquals(204, $response['headers']['status-code']);
+ $this->cleanupFunction($functionId);
}
public function testFunctionsDomain()
{
- $timeout = 15;
- $code = realpath(__DIR__ . '/../../../resources/functions') . "/php-cookie/code.tar.gz";
- $this->packageCode('php-cookie');
-
- $function = $this->client->call(Client::METHOD_POST, '/functions', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
+ $functionId = $this->setupFunction([
'functionId' => ID::unique(),
'name' => 'Test PHP Cookie executions',
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
- 'timeout' => $timeout,
+ 'timeout' => 15,
'execute' => ['any']
]);
- $functionId = $function['body']['$id'] ?? '';
-
- $this->assertEquals(201, $function['headers']['status-code']);
-
$rules = $this->client->call(Client::METHOD_GET, '/proxy/rules', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
@@ -2423,30 +1655,12 @@ class FunctionsCustomServerTest extends Scope
$domain = $rules['body']['rules'][0]['domain'];
- $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', array_merge([
- 'content-type' => 'multipart/form-data',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
+ $this->setupDeployment($functionId, [
'entrypoint' => 'index.php',
- 'code' => new CURLFile($code, 'application/x-gzip', basename($code)),
+ 'code' => $this->packageFunction('php-cookie'),
'activate' => true
]);
- $deploymentId = $deployment['body']['$id'] ?? '';
- $this->assertEquals(202, $deployment['headers']['status-code']);
-
- $this->awaitDeploymentIsBuilt($function['body']['$id'], $deploymentId, checkForSuccess: false);
-
- $deployment = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployments/' . $deploymentId, array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), []);
-
- $this->assertEquals(200, $deployment['headers']['status-code']);
-
- // Wait a little for activation to finish
- sleep(5);
-
$cookie = 'cookieName=cookieValue; cookie2=value2; cookie3=value=3; cookie4=val:ue4; cookie5=value5';
$proxyClient = new Client();
@@ -2464,64 +1678,31 @@ class FunctionsCustomServerTest extends Scope
// Await Aggregation
sleep(System::getEnv('_APP_USAGE_AGGREGATION_INTERVAL', 30));
- $tries = 0;
- while (true) {
- try {
- $response = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/usage', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id']
- ], $this->getHeaders()), [
- 'range' => '24h'
- ]);
+ $this->assertEventually(function () use ($functionId) {
+ $response = $this->getFunctionUsage($functionId, [
+ 'range' => '24h'
+ ]);
- $this->assertEquals(200, $response['headers']['status-code']);
- $this->assertEquals(19, count($response['body']));
- $this->assertEquals('24h', $response['body']['range']);
- $this->assertEquals(1, $response['body']['executionsTotal']);
+ $this->assertEquals(200, $response['headers']['status-code']);
+ $this->assertEquals(19, count($response['body']));
+ $this->assertEquals('24h', $response['body']['range']);
+ $this->assertEquals(1, $response['body']['executionsTotal']);
+ }, 25000, 1000);
- break;
- } catch (ExpectationFailedException $th) {
- if ($tries >= 5) {
- throw $th;
- } else {
- $tries++;
- sleep(5);
- }
- }
- }
-
- // Cleanup : Delete function
- $response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, [
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- 'x-appwrite-key' => $this->getProject()['apiKey'],
- ], []);
-
- $this->assertEquals(204, $response['headers']['status-code']);
+ $this->cleanupFunction($functionId);
}
public function testFunctionsDomainBinaryResponse()
{
- $timeout = 15;
- $code = realpath(__DIR__ . '/../../../resources/functions') . "/php-binary-response/code.tar.gz";
- $this->packageCode('php-binary-response');
-
- $function = $this->client->call(Client::METHOD_POST, '/functions', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
+ $functionId = $this->setupFunction([
'functionId' => ID::unique(),
'name' => 'Test PHP Binary executions',
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
- 'timeout' => $timeout,
+ 'timeout' => 15,
'execute' => ['any']
]);
- $functionId = $function['body']['$id'] ?? '';
-
- $this->assertEquals(201, $function['headers']['status-code']);
-
$rules = $this->client->call(Client::METHOD_GET, '/proxy/rules', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
@@ -2539,30 +1720,12 @@ class FunctionsCustomServerTest extends Scope
$domain = $rules['body']['rules'][0]['domain'];
- $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', array_merge([
- 'content-type' => 'multipart/form-data',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
+ $this->setupDeployment($functionId, [
'entrypoint' => 'index.php',
- 'code' => new CURLFile($code, 'application/x-gzip', basename($code)),
+ 'code' => $this->packageFunction('php-binary-response'),
'activate' => true
]);
- $deploymentId = $deployment['body']['$id'] ?? '';
- $this->assertEquals(202, $deployment['headers']['status-code']);
-
- $this->awaitDeploymentIsBuilt($function['body']['$id'], $deploymentId, checkForSuccess: false);
-
- $deployment = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployments/' . $deploymentId, array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), []);
-
- $this->assertEquals(200, $deployment['headers']['status-code']);
-
- // Wait a little for activation to finish
- sleep(5);
-
$proxyClient = new Client();
$proxyClient->setEndpoint('http://' . $domain);
@@ -2576,38 +1739,20 @@ class FunctionsCustomServerTest extends Scope
$this->assertEquals(10, $bytes['byte2']);
$this->assertEquals(255, $bytes['byte3']);
- // Cleanup : Delete function
- $response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, [
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- 'x-appwrite-key' => $this->getProject()['apiKey'],
- ], []);
-
- $this->assertEquals(204, $response['headers']['status-code']);
+ $this->cleanupFunction($functionId);
}
public function testFunctionsDomainBinaryRequest()
{
- $timeout = 15;
- $code = realpath(__DIR__ . '/../../../resources/functions') . "/php-binary-request/code.tar.gz";
- $this->packageCode('php-binary-request');
-
- $function = $this->client->call(Client::METHOD_POST, '/functions', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
+ $functionId = $this->setupFunction([
'functionId' => ID::unique(),
'name' => 'Test PHP Binary executions',
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
- 'timeout' => $timeout,
+ 'timeout' => 15,
'execute' => ['any']
]);
- $functionId = $function['body']['$id'] ?? '';
-
- $this->assertEquals(201, $function['headers']['status-code']);
-
$rules = $this->client->call(Client::METHOD_GET, '/proxy/rules', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
@@ -2625,30 +1770,12 @@ class FunctionsCustomServerTest extends Scope
$domain = $rules['body']['rules'][0]['domain'];
- $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', array_merge([
- 'content-type' => 'multipart/form-data',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
+ $this->setupDeployment($functionId, [
'entrypoint' => 'index.php',
- 'code' => new CURLFile($code, 'application/x-gzip', basename($code)),
+ 'code' => $this->packageFunction('php-binary-request'),
'activate' => true
]);
- $deploymentId = $deployment['body']['$id'] ?? '';
- $this->assertEquals(202, $deployment['headers']['status-code']);
-
- $this->awaitDeploymentIsBuilt($function['body']['$id'], $deploymentId, checkForSuccess: false);
-
- $deployment = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployments/' . $deploymentId, array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), []);
-
- $this->assertEquals(200, $deployment['headers']['status-code']);
-
- // Wait a little for activation to finish
- sleep(5);
-
$proxyClient = new Client();
$proxyClient->setEndpoint('http://' . $domain);
@@ -2659,18 +1786,12 @@ class FunctionsCustomServerTest extends Scope
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals(\md5($bytes), $response['body']);
- // Cleanup : Delete function
- $response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, [
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- 'x-appwrite-key' => $this->getProject()['apiKey'],
- ], []);
-
- $this->assertEquals(204, $response['headers']['status-code']);
+ $this->cleanupFunction($functionId);
}
- public function testCreateFunctionWithResponseFormatHeader()
+ public function testResponseFilters()
{
+ // create function with 1.5.0 response format
$response = $this->client->call(Client::METHOD_POST, '/functions', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
@@ -2684,28 +1805,75 @@ class FunctionsCustomServerTest extends Scope
]);
$this->assertEquals(201, $response['headers']['status-code']);
+ $this->assertArrayNotHasKey('scopes', $response['body']);
+ $this->assertArrayNotHasKey('specification', $response['body']);
- // Cleanup : Delete function
- $response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $response['body']['$id'], [
+ // get function with 1.5.0 response format header
+ $function = $this->client->call(Client::METHOD_GET, '/functions/' . $response['body']['$id'], array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
- 'x-appwrite-key' => $this->getProject()['apiKey'],
- ], []);
+ 'x-appwrite-response-format' => '1.5.0', // add response format header
+ ], $this->getHeaders()));
- $this->assertEquals(204, $response['headers']['status-code']);
+ $this->assertEquals(200, $function['headers']['status-code']);
+ $this->assertArrayNotHasKey('scopes', $function['body']);
+ $this->assertArrayNotHasKey('specification', $function['body']);
+
+ $function = $this->getFunction($function['body']['$id']);
+
+ $this->assertEquals(200, $function['headers']['status-code']);
+ $this->assertArrayHasKey('scopes', $function['body']);
+ $this->assertArrayHasKey('specification', $function['body']);
+
+ $functionId = $function['body']['$id'] ?? '';
+ $this->cleanupFunction($functionId);
+ }
+
+ public function testRequestFilters()
+ {
+ $function1Id = $this->setupFunction([
+ 'functionId' => ID::unique(),
+ 'name' => 'Test',
+ 'runtime' => 'php-8.0',
+ 'entrypoint' => 'index.php',
+ 'timeout' => 15,
+ 'execute' => ['any']
+ ]);
+
+ $function2Id = $this->setupFunction([
+ 'functionId' => ID::unique(),
+ 'name' => 'Test2',
+ 'runtime' => 'php-8.0',
+ 'entrypoint' => 'index.php',
+ 'timeout' => 15,
+ 'execute' => ['any']
+ ]);
+
+ // list functions using request filters
+ $response = $this->client->call(
+ Client::METHOD_GET,
+ '/functions',
+ array_merge([
+ 'content-type' => 'application/json',
+ 'x-appwrite-project' => $this->getProject()['$id'],
+ 'x-appwrite-response-format' => '1.4.0', // Set response format for 1.4 syntax
+ ], $this->getHeaders()),
+ [
+ 'queries' => [ 'equal("name", ["Test2"])' ]
+ ]
+ );
+
+ $this->assertEquals(200, $response['headers']['status-code']);
+ $this->assertCount(1, $response['body']['functions']);
+ $this->assertEquals('Test2', $response['body']['functions'][0]['name']);
+
+ $this->cleanupFunction($function1Id);
+ $this->cleanupFunction($function2Id);
}
public function testFunctionLogging()
{
- // Preparations: Create Function
- $folder = 'node';
- $code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz";
- $this->packageCode($folder);
-
- $function = $this->client->call(Client::METHOD_POST, '/functions', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
+ $function = $this->createFunction([
'functionId' => ID::unique(),
'runtime' => 'node-18.0',
'name' => 'Logging Test',
@@ -2718,38 +1886,22 @@ class FunctionsCustomServerTest extends Scope
$this->assertFalse($function['body']['logging']);
$this->assertNotEmpty($function['body']['$id']);
- $functionId = $function['body']['$id'];
+ $functionId = $functionId = $function['body']['$id'] ?? '';
- $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', array_merge([
- 'content-type' => 'multipart/form-data',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
- 'code' => new CURLFile($code, 'application/x-gzip', basename($code)),
+ $this->setupDeployment($functionId, [
+ 'code' => $this->packageFunction('node'),
'activate' => true
]);
- $this->assertEquals(202, $deployment['headers']['status-code']);
- $this->assertNotEmpty($deployment['body']['$id']);
-
- $deploymentId = $deployment['body']['$id'] ?? '';
-
- $this->awaitDeploymentIsBuilt($functionId, $deploymentId, checkForSuccess: false);
-
// Sync Executions test
- $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), []);
+ $execution = $this->createExecution($functionId);
$this->assertEquals(201, $execution['headers']['status-code']);
$this->assertEmpty($execution['body']['logs']);
$this->assertEmpty($execution['body']['errors']);
// Async Executions test
- $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
+ $execution = $this->createExecution($functionId, [
'async' => true
]);
@@ -2758,19 +1910,16 @@ class FunctionsCustomServerTest extends Scope
$this->assertEmpty($execution['body']['errors']);
$this->assertNotEmpty($execution['body']['$id']);
- $executionId = $execution['body']['$id'];
+ $executionId = $execution['body']['$id'] ?? '';
- sleep(5);
+ $this->assertEventually(function () use ($functionId, $executionId) {
+ $execution = $this->getExecution($functionId, $executionId);
- $execution = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions/' . $executionId, array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()));
-
- $this->assertEquals(200, $execution['headers']['status-code']);
- $this->assertEquals('completed', $execution['body']['status']);
- $this->assertEmpty($execution['body']['logs']);
- $this->assertEmpty($execution['body']['errors']);
+ $this->assertEquals(200, $execution['headers']['status-code']);
+ $this->assertEquals('completed', $execution['body']['status']);
+ $this->assertEmpty($execution['body']['logs']);
+ $this->assertEmpty($execution['body']['errors']);
+ }, 10000, 500);
// Domain Executions test
$rules = $this->client->call(Client::METHOD_GET, '/proxy/rules', array_merge([
@@ -2798,10 +1947,7 @@ class FunctionsCustomServerTest extends Scope
$this->assertEquals(200, $response['headers']['status-code']);
- $executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()), [
+ $executions = $this->listExecutions($functionId, [
'queries' => [
Query::limit(1)->toString(),
Query::orderDesc('$id')->toString(),
@@ -2814,10 +1960,7 @@ class FunctionsCustomServerTest extends Scope
$this->assertEmpty($executions['body']['executions'][0]['errors']);
// Ensure executions count
- $executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', array_merge([
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- ], $this->getHeaders()));
+ $executions = $this->listExecutions($functionId);
$this->assertEquals(200, $executions['headers']['status-code']);
$this->assertCount(3, $executions['body']['executions']);
@@ -2828,13 +1971,6 @@ class FunctionsCustomServerTest extends Scope
$this->assertEmpty($execution['errors']);
}
- // Cleanup : Delete function
- $response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, [
- 'content-type' => 'application/json',
- 'x-appwrite-project' => $this->getProject()['$id'],
- 'x-appwrite-key' => $this->getProject()['apiKey'],
- ], []);
-
- $this->assertEquals(204, $response['headers']['status-code']);
+ $this->cleanupFunction($functionId);
}
}
diff --git a/tests/e2e/Services/FunctionsSchedule/FunctionsScheduleTest.php b/tests/e2e/Services/FunctionsSchedule/FunctionsScheduleTest.php
new file mode 100644
index 0000000000..b1315103b1
--- /dev/null
+++ b/tests/e2e/Services/FunctionsSchedule/FunctionsScheduleTest.php
@@ -0,0 +1,214 @@
+setupFunction([
+ 'functionId' => ID::unique(),
+ 'name' => 'Test',
+ 'execute' => [Role::user($this->getUser()['$id'])->toString()],
+ 'runtime' => 'php-8.0',
+ 'entrypoint' => 'index.php',
+ 'events' => [
+ 'users.*.create',
+ 'users.*.delete',
+ ],
+ 'schedule' => '* * * * *', // Execute every 60 seconds
+ 'timeout' => 10,
+ ]);
+
+ $this->setupDeployment($functionId, [
+ 'entrypoint' => 'index.php',
+ 'code' => $this->packageFunction('php'),
+ 'activate' => true
+ ]);
+
+ // Wait for scheduled execution
+ \sleep(60);
+
+ $this->assertEventually(function () use ($functionId) {
+ $executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', [
+ 'content-type' => 'application/json',
+ 'x-appwrite-project' => $this->getProject()['$id'],
+ 'x-appwrite-key' => $this->getProject()['apiKey'],
+ ]);
+
+ $this->assertEquals(200, $executions['headers']['status-code']);
+ $this->assertCount(1, $executions['body']['executions']);
+
+ $asyncExecution = $executions['body']['executions'][0];
+
+ $this->assertEquals('schedule', $asyncExecution['trigger']);
+ $this->assertEquals('completed', $asyncExecution['status']);
+ $this->assertEquals(200, $asyncExecution['responseStatusCode']);
+ $this->assertEquals('', $asyncExecution['responseBody']);
+ $this->assertNotEmpty($asyncExecution['logs']);
+ $this->assertNotEmpty($asyncExecution['errors']);
+ $this->assertGreaterThan(0, $asyncExecution['duration']);
+ }, 60000, 500);
+
+ $this->cleanupFunction($functionId);
+ }
+
+ public function testCreateScheduledAtExecution(): void
+ {
+ /**
+ * Test for SUCCESS
+ */
+ $functionId = $this->setupFunction([
+ 'functionId' => ID::unique(),
+ 'name' => 'Test',
+ 'execute' => [Role::user($this->getUser()['$id'])->toString()],
+ 'runtime' => 'php-8.0',
+ 'entrypoint' => 'index.php',
+ 'timeout' => 10,
+ 'logging' => true,
+ ]);
+ $this->setupDeployment($functionId, [
+ 'entrypoint' => 'index.php',
+ 'code' => $this->packageFunction('php'),
+ 'activate' => true
+ ]);
+
+ // Schedule execution for the future
+ \date_default_timezone_set('UTC');
+ $futureTime = (new \DateTime())->add(new \DateInterval('PT2M')); // 2 minute in the future
+ $futureTime->setTime($futureTime->format('H'), $futureTime->format('i'), 0, 0);
+
+
+ $execution = $this->client->call(
+ Client::METHOD_POST,
+ '/functions/' . $functionId . '/executions',
+ [
+ 'content-type' => 'application/json',
+ 'x-appwrite-project' => $this->getProject()['$id'],
+ 'origin' => 'http://localhost',
+ 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $this->getUser()['session'],
+ ],
+ [
+ 'async' => true,
+ 'scheduledAt' => $futureTime->format(\DateTime::ATOM),
+ 'path' => '/custom-path',
+ 'method' => 'PATCH',
+ 'body' => 'custom-body',
+ 'headers' => [
+ 'x-custom-header' => 'custom-value'
+ ]
+ ]
+ );
+ $executionId = $execution['body']['$id'];
+
+ $this->assertEquals(202, $execution['headers']['status-code']);
+ $this->assertEquals('scheduled', $execution['body']['status']);
+ $this->assertEquals('PATCH', $execution['body']['requestMethod']);
+ $this->assertEquals('/custom-path', $execution['body']['requestPath']);
+ $this->assertCount(0, $execution['body']['requestHeaders']);
+
+ \sleep(120);
+
+ $this->assertEventually(function () use ($functionId, $executionId) {
+ $execution = $this->getExecution($functionId, $executionId);
+
+ $this->assertEquals(200, $execution['headers']['status-code']);
+ $this->assertEquals(200, $execution['body']['responseStatusCode']);
+ $this->assertEquals('completed', $execution['body']['status']);
+ $this->assertEquals('/custom-path', $execution['body']['requestPath']);
+ $this->assertEquals('PATCH', $execution['body']['requestMethod']);
+ $this->assertStringContainsString('body-is-custom-body', $execution['body']['logs']);
+ $this->assertStringContainsString('custom-header-is-custom-value', $execution['body']['logs']);
+ $this->assertStringContainsString('method-is-patch', $execution['body']['logs']);
+ $this->assertStringContainsString('path-is-/custom-path', $execution['body']['logs']);
+ $this->assertStringContainsString('user-is-' . $this->getUser()['$id'], $execution['body']['logs']);
+ $this->assertStringContainsString('jwt-is-valid', $execution['body']['logs']);
+ $this->assertGreaterThan(0, $execution['body']['duration']);
+ }, 10000, 500);
+
+ /* Test for FAILURE */
+ // Schedule synchronous execution
+ $execution = $this->createExecution($functionId, [
+ 'async' => 'false',
+ 'scheduledAt' => $futureTime->format(\DateTime::ATOM),
+ ]);
+ $this->assertEquals(400, $execution['headers']['status-code']);
+
+ // Execution with seconds precision
+ $execution = $this->createExecution($functionId, [
+ 'async' => true,
+ 'scheduledAt' => (new \DateTime("2100-12-08 16:12:02"))->format(\DateTime::ATOM)
+ ]);
+ $this->assertEquals(400, $execution['headers']['status-code']);
+
+ // Execution with milliseconds precision
+ $execution = $this->createExecution($functionId, [
+ 'async' => true,
+ 'scheduledAt' => (new \DateTime("2100-12-08 16:12:02.255"))->format(\DateTime::ATOM)
+ ]);
+ $this->assertEquals(400, $execution['headers']['status-code']);
+
+ // Execution too soon
+ $execution = $this->createExecution($functionId, [
+ 'async' => true,
+ 'scheduledAt' => (new \DateTime())->add(new \DateInterval('PT1S'))->format(\DateTime::ATOM)
+ ]);
+ $this->assertEquals(400, $execution['headers']['status-code']);
+
+ $this->cleanupFunction($functionId, $executionId);
+ }
+
+ public function testDeleteScheduledExecution()
+ {
+ $functionId = $this->setupFunction([
+ 'functionId' => ID::unique(),
+ 'name' => 'Test',
+ 'execute' => [Role::user($this->getUser()['$id'])->toString()],
+ 'runtime' => 'php-8.0',
+ 'entrypoint' => 'index.php',
+ 'timeout' => 10,
+ 'logging' => true,
+ ]);
+
+ $this->setupDeployment($functionId, [
+ 'entrypoint' => 'index.php',
+ 'code' => $this->packageFunction('php'),
+ 'activate' => true
+ ]);
+
+ $futureTime = (new \DateTime())->add(new \DateInterval('PT10H'));
+ $futureTime->setTime($futureTime->format('H'), $futureTime->format('i'), 0, 0);
+
+ $execution = $this->createExecution($functionId, [
+ 'async' => true,
+ 'scheduledAt' => $futureTime->format('Y-m-d H:i:s'),
+ ]);
+
+ $this->assertEquals(202, $execution['headers']['status-code']);
+
+ $executionId = $execution['body']['$id'] ?? '';
+
+ $execution = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId . '/executions/' . $executionId, array_merge([
+ 'content-type' => 'application/json',
+ 'x-appwrite-project' => $this->getProject()['$id'],
+ ], $this->getHeaders()));
+
+ $this->assertEquals(204, $execution['headers']['status-code']);
+
+ $this->cleanupFunction($functionId);
+ }
+}
diff --git a/tests/e2e/Services/GraphQL/Base.php b/tests/e2e/Services/GraphQL/Base.php
index b77f006bf8..0b3250cecf 100644
--- a/tests/e2e/Services/GraphQL/Base.php
+++ b/tests/e2e/Services/GraphQL/Base.php
@@ -2,6 +2,7 @@
namespace Tests\E2E\Services\GraphQL;
+use CURLFile;
use Utopia\CLI\Console;
trait Base
@@ -2496,8 +2497,17 @@ trait Base
protected string $stdout = '';
protected string $stderr = '';
- protected function packageCode($folder): void
+ protected function packageFunction(string $function): CURLFile
{
- Console::execute('cd ' . realpath(__DIR__ . "/../../../resources/functions") . "/$folder && tar --exclude code.tar.gz -czf code.tar.gz .", '', $this->stdout);
+ $folderPath = realpath(__DIR__ . '/../../../resources/functions') . "/$function";
+ $tarPath = "$folderPath/code.tar.gz";
+
+ Console::execute("cd $folderPath && tar --exclude code.tar.gz -czf code.tar.gz .", '', $this->stdout, $this->stderr);
+
+ if (filesize($tarPath) > 1024 * 1024 * 5) {
+ throw new \Exception('Code package is too large. Use the chunked upload method instead.');
+ }
+
+ return new CURLFile($tarPath, 'application/x-gzip', \basename($tarPath));
}
}
diff --git a/tests/e2e/Services/GraphQL/FunctionsClientTest.php b/tests/e2e/Services/GraphQL/FunctionsClientTest.php
index 3f0ee1966a..e7e8421254 100644
--- a/tests/e2e/Services/GraphQL/FunctionsClientTest.php
+++ b/tests/e2e/Services/GraphQL/FunctionsClientTest.php
@@ -2,7 +2,6 @@
namespace Tests\E2E\Services\GraphQL;
-use CURLFile;
use Tests\E2E\Client;
use Tests\E2E\Scopes\ProjectCustom;
use Tests\E2E\Scopes\Scope;
@@ -83,10 +82,6 @@ class FunctionsClientTest extends Scope
$projectId = $this->getProject()['$id'];
$query = $this->getQuery(self::$CREATE_DEPLOYMENT);
- $folder = 'php';
- $code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz";
- $this->packageCode($folder);
-
$gqlPayload = [
'operations' => \json_encode([
'query' => $query,
@@ -99,7 +94,7 @@ class FunctionsClientTest extends Scope
'map' => \json_encode([
'code' => ["variables.code"]
]),
- 'code' => new CURLFile($code, 'application/gzip', 'code.tar.gz'),
+ 'code' => $this->packageFunction('php')
];
$deployment = $this->client->call(Client::METHOD_POST, '/graphql', [
diff --git a/tests/e2e/Services/GraphQL/FunctionsServerTest.php b/tests/e2e/Services/GraphQL/FunctionsServerTest.php
index 25a671fa1c..c3606244c4 100644
--- a/tests/e2e/Services/GraphQL/FunctionsServerTest.php
+++ b/tests/e2e/Services/GraphQL/FunctionsServerTest.php
@@ -2,7 +2,6 @@
namespace Tests\E2E\Services\GraphQL;
-use CURLFile;
use Tests\E2E\Client;
use Tests\E2E\Scopes\ProjectCustom;
use Tests\E2E\Scopes\Scope;
@@ -82,10 +81,6 @@ class FunctionsServerTest extends Scope
$projectId = $this->getProject()['$id'];
$query = $this->getQuery(self::$CREATE_DEPLOYMENT);
- $folder = 'php';
- $code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz";
- $this->packageCode($folder);
-
$gqlPayload = [
'operations' => \json_encode([
'query' => $query,
@@ -98,7 +93,7 @@ class FunctionsServerTest extends Scope
'map' => \json_encode([
'code' => ["variables.code"]
]),
- 'code' => new CURLFile($code, 'application/gzip', 'code.tar.gz'),
+ 'code' => $this->packageFunction('php'),
];
$deployment = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
diff --git a/tests/e2e/Services/GraphQL/StorageClientTest.php b/tests/e2e/Services/GraphQL/StorageClientTest.php
index 7d6d617c87..9896598c2d 100644
--- a/tests/e2e/Services/GraphQL/StorageClientTest.php
+++ b/tests/e2e/Services/GraphQL/StorageClientTest.php
@@ -183,6 +183,7 @@ class StorageClientTest extends Scope
/**
* @depends testCreateFile
* @param $file
+ * @return array
* @throws \Exception
*/
public function testGetFileDownload($file)
diff --git a/tests/e2e/Services/GraphQL/StorageServerTest.php b/tests/e2e/Services/GraphQL/StorageServerTest.php
index 6be103629e..7fea895b1c 100644
--- a/tests/e2e/Services/GraphQL/StorageServerTest.php
+++ b/tests/e2e/Services/GraphQL/StorageServerTest.php
@@ -232,6 +232,7 @@ class StorageServerTest extends Scope
/**
* @depends testCreateFile
* @param $file
+ * @return array
* @throws \Exception
*/
public function testGetFileDownload($file)
diff --git a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php
index 9fc862e85b..05893be3a8 100644
--- a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php
+++ b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php
@@ -101,7 +101,7 @@ class ProjectsConsoleClientTest extends Scope
'region' => 'default'
]);
- $this->assertEquals(400, $response['headers']['status-code']);
+ $this->assertEquals(401, $response['headers']['status-code']);
return [
'projectId' => $projectId,
@@ -546,7 +546,7 @@ class ProjectsConsoleClientTest extends Scope
'name' => '',
]);
- $this->assertEquals(400, $response['headers']['status-code']);
+ $this->assertEquals(401, $response['headers']['status-code']);
return ['projectId' => $projectId];
}
@@ -1691,7 +1691,7 @@ class ProjectsConsoleClientTest extends Scope
]);
$this->assertEquals(400, $response['headers']['status-code']);
- $this->assertEquals('Invalid `numbers` param: Value must a valid array no longer than 10 items', $response['body']['message']);
+ $this->assertEquals('Invalid `numbers` param: Value must a valid array no longer than 10 items and Phone number must start with a \'+\' can have a maximum of fifteen digits.', $response['body']['message']);
/**
* Test for success
diff --git a/tests/e2e/Services/Realtime/RealtimeBase.php b/tests/e2e/Services/Realtime/RealtimeBase.php
index 30c411ba93..99f31134c0 100644
--- a/tests/e2e/Services/Realtime/RealtimeBase.php
+++ b/tests/e2e/Services/Realtime/RealtimeBase.php
@@ -7,25 +7,34 @@ use WebSocket\ConnectionException;
trait RealtimeBase
{
- private function getWebsocket($channels = [], $headers = [], $projectId = null): WebSocketClient
- {
+ private function getWebsocket(
+ array $channels = [],
+ array $headers = [],
+ string $projectId = null
+ ): WebSocketClient {
if (is_null($projectId)) {
$projectId = $this->getProject()['$id'];
}
- $headers = array_merge([
- 'Origin' => 'appwrite.test'
- ], $headers);
+ $headers = array_merge(
+ [
+ "Origin" => "appwrite.test",
+ ],
+ $headers
+ );
$query = [
- 'project' => $projectId,
- 'channels' => $channels
+ "project" => $projectId,
+ "channels" => $channels,
];
- return new WebSocketClient('ws://appwrite-traefik/v1/realtime?' . http_build_query($query), [
- 'headers' => $headers,
- 'timeout' => 30,
- ]);
+ return new WebSocketClient(
+ "ws://appwrite-traefik/v1/realtime?" . http_build_query($query),
+ [
+ "headers" => $headers,
+ "timeout" => 30,
+ ]
+ );
}
public function testConnection(): void
@@ -33,7 +42,7 @@ trait RealtimeBase
/**
* Test for SUCCESS
*/
- $client = $this->getWebsocket(['documents']);
+ $client = $this->getWebsocket(["documents"]);
$this->assertNotEmpty($client->receive());
$client->close();
}
@@ -43,11 +52,11 @@ trait RealtimeBase
$client = $this->getWebsocket();
$payload = json_decode($client->receive(), true);
- $this->assertArrayHasKey('type', $payload);
- $this->assertArrayHasKey('data', $payload);
- $this->assertEquals('error', $payload['type']);
- $this->assertEquals(1008, $payload['data']['code']);
- $this->assertEquals('Missing channels', $payload['data']['message']);
+ $this->assertArrayHasKey("type", $payload);
+ $this->assertArrayHasKey("data", $payload);
+ $this->assertEquals("error", $payload["type"]);
+ $this->assertEquals(1008, $payload["data"]["code"]);
+ $this->assertEquals("Missing channels", $payload["data"]["message"]);
\usleep(250000); // 250ms
$this->expectException(ConnectionException::class); // Check if server disconnnected client
$client->close();
@@ -55,18 +64,24 @@ trait RealtimeBase
public function testConnectionFailureUnknownProject(): void
{
- $client = new WebSocketClient('ws://appwrite-traefik/v1/realtime?project=123', [
- 'headers' => [
- 'Origin' => 'appwrite.test'
+ $client = new WebSocketClient(
+ "ws://appwrite-traefik/v1/realtime?project=123",
+ [
+ "headers" => [
+ "Origin" => "appwrite.test",
+ ],
]
- ]);
+ );
$payload = json_decode($client->receive(), true);
- $this->assertArrayHasKey('type', $payload);
- $this->assertArrayHasKey('data', $payload);
- $this->assertEquals('error', $payload['type']);
- $this->assertEquals(1008, $payload['data']['code']);
- $this->assertEquals('Missing or unknown project ID', $payload['data']['message']);
+ $this->assertArrayHasKey("type", $payload);
+ $this->assertArrayHasKey("data", $payload);
+ $this->assertEquals("error", $payload["type"]);
+ $this->assertEquals(1008, $payload["data"]["code"]);
+ $this->assertEquals(
+ "Missing or unknown project ID",
+ $payload["data"]["message"]
+ );
\usleep(250000); // 250ms
$this->expectException(ConnectionException::class); // Check if server disconnnected client
$client->close();
diff --git a/tests/e2e/Services/Realtime/RealtimeConsoleClientTest.php b/tests/e2e/Services/Realtime/RealtimeConsoleClientTest.php
index 6ab2874f8e..60c96c6e19 100644
--- a/tests/e2e/Services/Realtime/RealtimeConsoleClientTest.php
+++ b/tests/e2e/Services/Realtime/RealtimeConsoleClientTest.php
@@ -2,7 +2,6 @@
namespace Tests\E2E\Services\Realtime;
-use CURLFile;
use Tests\E2E\Client;
use Tests\E2E\Scopes\ProjectCustom;
use Tests\E2E\Scopes\Scope;
@@ -19,7 +18,7 @@ class RealtimeConsoleClientTest extends Scope
use ProjectCustom;
use SideConsole;
- public function testManualAuthentication()
+ public function testManualAuthentication(): void
{
$user = $this->getUser();
$userId = $user['$id'] ?? '';
@@ -124,7 +123,7 @@ class RealtimeConsoleClientTest extends Scope
$client->close();
}
- public function testAttributes()
+ public function testAttributes(): array
{
$user = $this->getUser();
$projectId = 'console';
@@ -184,6 +183,7 @@ class RealtimeConsoleClientTest extends Scope
'required' => true,
]);
+ $projectId = $this->getProject()['$id'];
$attributeKey = $name['body']['key'];
$this->assertEquals($name['headers']['status-code'], 202);
@@ -198,8 +198,9 @@ class RealtimeConsoleClientTest extends Scope
$this->assertEquals('event', $response['type']);
$this->assertNotEmpty($response['data']);
$this->assertArrayHasKey('timestamp', $response['data']);
- $this->assertCount(1, $response['data']['channels']);
+ $this->assertCount(2, $response['data']['channels']);
$this->assertContains('console', $response['data']['channels']);
+ $this->assertContains("projects.{$projectId}", $response['data']['channels']);
$this->assertContains("databases.{$databaseId}.collections.{$actorsId}.attributes.*.create", $response['data']['events']);
$this->assertContains("databases.{$databaseId}.collections.{$actorsId}.attributes.*", $response['data']['events']);
$this->assertContains("databases.{$databaseId}.collections.{$actorsId}", $response['data']['events']);
@@ -218,8 +219,9 @@ class RealtimeConsoleClientTest extends Scope
$this->assertEquals('event', $response['type']);
$this->assertNotEmpty($response['data']);
$this->assertArrayHasKey('timestamp', $response['data']);
- $this->assertCount(1, $response['data']['channels']);
+ $this->assertCount(2, $response['data']['channels']);
$this->assertContains('console', $response['data']['channels']);
+ $this->assertContains("projects.{$projectId}", $response['data']['channels']);
$this->assertContains("databases.{$databaseId}.collections.{$actorsId}.attributes.*.update", $response['data']['events']);
$this->assertContains("databases.{$databaseId}.collections.{$actorsId}.attributes.*", $response['data']['events']);
$this->assertContains("databases.{$databaseId}.collections.{$actorsId}", $response['data']['events']);
@@ -276,6 +278,8 @@ class RealtimeConsoleClientTest extends Scope
]);
$this->assertEquals($index['headers']['status-code'], 202);
+
+ $projectId = $this->getProject()['$id'];
$indexKey = $index['body']['key'];
$response = json_decode($client->receive(), true);
@@ -285,8 +289,9 @@ class RealtimeConsoleClientTest extends Scope
$this->assertEquals('event', $response['type']);
$this->assertNotEmpty($response['data']);
$this->assertArrayHasKey('timestamp', $response['data']);
- $this->assertCount(1, $response['data']['channels']);
+ $this->assertCount(2, $response['data']['channels']);
$this->assertContains('console', $response['data']['channels']);
+ $this->assertContains("projects.{$projectId}", $response['data']['channels']);
$this->assertContains("databases.{$databaseId}.collections.{$actorsId}.indexes.*.create", $response['data']['events']);
$this->assertContains("databases.{$databaseId}.collections.{$actorsId}.indexes.*", $response['data']['events']);
$this->assertContains("databases.{$databaseId}.collections.{$actorsId}", $response['data']['events']);
@@ -303,8 +308,9 @@ class RealtimeConsoleClientTest extends Scope
$this->assertEquals('event', $response['type']);
$this->assertNotEmpty($response['data']);
$this->assertArrayHasKey('timestamp', $response['data']);
- $this->assertCount(1, $response['data']['channels']);
+ $this->assertCount(2, $response['data']['channels']);
$this->assertContains('console', $response['data']['channels']);
+ $this->assertContains("projects.{$projectId}", $response['data']['channels']);
$this->assertContains("databases.{$databaseId}.collections.{$actorsId}.indexes.*.update", $response['data']['events']);
$this->assertContains("databases.{$databaseId}.collections.{$actorsId}.indexes.*", $response['data']['events']);
$this->assertContains("databases.{$databaseId}.collections.{$actorsId}", $response['data']['events']);
@@ -343,6 +349,8 @@ class RealtimeConsoleClientTest extends Scope
$this->assertContains('console', $response['data']['channels']);
$this->assertNotEmpty($response['data']['user']);
+ $projectId = $this->getProject()['$id'];
+
/**
* Test Delete Index
*/
@@ -353,6 +361,7 @@ class RealtimeConsoleClientTest extends Scope
], $this->getHeaders()));
$this->assertEquals($attribute['headers']['status-code'], 204);
+
$response = json_decode($client->receive(), true);
$this->assertArrayHasKey('type', $response);
@@ -360,8 +369,9 @@ class RealtimeConsoleClientTest extends Scope
$this->assertEquals('event', $response['type']);
$this->assertNotEmpty($response['data']);
$this->assertArrayHasKey('timestamp', $response['data']);
- $this->assertCount(1, $response['data']['channels']);
+ $this->assertCount(2, $response['data']['channels']);
$this->assertContains('console', $response['data']['channels']);
+ $this->assertContains("projects.{$projectId}", $response['data']['channels']);
$this->assertContains("databases.{$databaseId}.collections.{$actorsId}.indexes.*.update", $response['data']['events']);
$this->assertContains("databases.{$databaseId}.collections.{$actorsId}.indexes.*", $response['data']['events']);
$this->assertContains("databases.{$databaseId}.collections.{$actorsId}", $response['data']['events']);
@@ -377,8 +387,9 @@ class RealtimeConsoleClientTest extends Scope
$this->assertEquals('event', $response['type']);
$this->assertNotEmpty($response['data']);
$this->assertArrayHasKey('timestamp', $response['data']);
- $this->assertCount(1, $response['data']['channels']);
+ $this->assertCount(2, $response['data']['channels']);
$this->assertContains('console', $response['data']['channels']);
+ $this->assertContains("projects.{$projectId}", $response['data']['channels']);
$this->assertContains("databases.{$databaseId}.collections.{$actorsId}.indexes.*.delete", $response['data']['events']);
$this->assertContains("databases.{$databaseId}.collections.{$actorsId}.indexes.*", $response['data']['events']);
$this->assertContains("databases.{$databaseId}.collections.{$actorsId}", $response['data']['events']);
@@ -416,10 +427,12 @@ class RealtimeConsoleClientTest extends Scope
$this->assertContains('console', $response['data']['channels']);
$this->assertNotEmpty($response['data']['user']);
+ $attributeKey = 'name';
+ $projectId = $this->getProject()['$id'];
+
/**
* Test Delete Attribute
*/
- $attributeKey = 'name';
$attribute = $this->client->call(Client::METHOD_DELETE, '/databases/' . $databaseId . '/collections/' . $data['actorsId'] . '/attributes/' . $attributeKey, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
@@ -433,8 +446,9 @@ class RealtimeConsoleClientTest extends Scope
$this->assertEquals('event', $response['type']);
$this->assertNotEmpty($response['data']);
$this->assertArrayHasKey('timestamp', $response['data']);
- $this->assertCount(1, $response['data']['channels']);
+ $this->assertCount(2, $response['data']['channels']);
$this->assertContains('console', $response['data']['channels']);
+ $this->assertContains("projects.{$projectId}", $response['data']['channels']);
$this->assertContains("databases.{$databaseId}.collections.{$actorsId}.attributes.*.update", $response['data']['events']);
$this->assertContains("databases.{$databaseId}.collections.{$actorsId}.attributes.*", $response['data']['events']);
$this->assertContains("databases.{$databaseId}.collections.{$actorsId}", $response['data']['events']);
@@ -450,8 +464,9 @@ class RealtimeConsoleClientTest extends Scope
$this->assertEquals('event', $response['type']);
$this->assertNotEmpty($response['data']);
$this->assertArrayHasKey('timestamp', $response['data']);
- $this->assertCount(1, $response['data']['channels']);
+ $this->assertCount(2, $response['data']['channels']);
$this->assertContains('console', $response['data']['channels']);
+ $this->assertContains("projects.{$projectId}", $response['data']['channels']);
$this->assertContains("databases.{$databaseId}.collections.{$actorsId}.attributes.*.delete", $response['data']['events']);
$this->assertContains("databases.{$databaseId}.collections.{$actorsId}.attributes.*", $response['data']['events']);
$this->assertContains("databases.{$databaseId}.collections.{$actorsId}", $response['data']['events']);
@@ -505,22 +520,16 @@ class RealtimeConsoleClientTest extends Scope
/**
* Test Create Deployment
*/
-
- $folder = 'php';
- $code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz";
- $this->packageCode($folder);
-
+ $projectId = $this->getProject()['$id'];
$deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', array_merge([
'content-type' => 'multipart/form-data',
- 'x-appwrite-project' => $this->getProject()['$id'],
+ 'x-appwrite-project' => $projectId,
], $this->getHeaders()), [
'entrypoint' => 'index.php',
- 'code' => new CURLFile($code, 'application/x-gzip', \basename($code)),
+ 'code' => $this->packageFunction('php'),
'activate' => true
]);
- $deploymentId = $deployment['body']['$id'] ?? '';
-
$this->assertEquals(202, $deployment['headers']['status-code']);
$response = json_decode($client->receive(), true);
@@ -530,8 +539,9 @@ class RealtimeConsoleClientTest extends Scope
$this->assertEquals('event', $response['type']);
$this->assertNotEmpty($response['data']);
$this->assertArrayHasKey('timestamp', $response['data']);
- $this->assertCount(1, $response['data']['channels']);
+ $this->assertCount(2, $response['data']['channels']);
$this->assertContains('console', $response['data']['channels']);
+ $this->assertContains("projects.{$projectId}", $response['data']['channels']);
// $this->assertContains("functions.{$functionId}.deployments.{$deploymentId}.create", $response['data']['events']); TODO @christyjacob4 : enable test once we allow functions.* events
$this->assertNotEmpty($response['data']['payload']);
diff --git a/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php b/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php
index 86aaee2ab0..616f309fd2 100644
--- a/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php
+++ b/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php
@@ -7,7 +7,7 @@ use Tests\E2E\Client;
use Tests\E2E\Scopes\ProjectCustom;
use Tests\E2E\Scopes\Scope;
use Tests\E2E\Scopes\SideClient;
-use Utopia\CLI\Console;
+use Tests\E2E\Services\Functions\FunctionsBase;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
@@ -15,6 +15,7 @@ use WebSocket\ConnectionException;
class RealtimeCustomClientTest extends Scope
{
+ use FunctionsBase;
use RealtimeBase;
use ProjectCustom;
use SideClient;
@@ -1271,19 +1272,13 @@ class RealtimeCustomClientTest extends Scope
$this->assertEquals($function['headers']['status-code'], 201);
$this->assertNotEmpty($function['body']['$id']);
- $folder = 'timeout';
- $output = '';
- $code = realpath(__DIR__ . '/../../../resources/functions') . "/{$folder}/code.tar.gz";
-
- Console::execute('cd ' . realpath(__DIR__ . "/../../../resources/functions") . "/{$folder} && tar --exclude code.tar.gz -czf code.tar.gz .", '', $output);
-
$deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', array_merge([
'content-type' => 'multipart/form-data',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'entrypoint' => 'index.php',
- 'code' => new CURLFile($code, 'application/x-gzip', basename($code)),
+ 'code' => $this->packageFunction('timeout'),
'activate' => true
]);
@@ -1339,8 +1334,9 @@ class RealtimeCustomClientTest extends Scope
$this->assertEquals('event', $response['type']);
$this->assertNotEmpty($response['data']);
$this->assertArrayHasKey('timestamp', $response['data']);
- $this->assertCount(4, $response['data']['channels']);
+ $this->assertCount(5, $response['data']['channels']);
$this->assertContains('console', $response['data']['channels']);
+ $this->assertContains("projects.{$this->getProject()['$id']}", $response['data']['channels']);
$this->assertContains('executions', $response['data']['channels']);
$this->assertContains("executions.{$executionId}", $response['data']['channels']);
$this->assertContains("functions.{$functionId}", $response['data']['channels']);
@@ -1361,8 +1357,9 @@ class RealtimeCustomClientTest extends Scope
$this->assertEquals('event', $responseUpdate['type']);
$this->assertNotEmpty($responseUpdate['data']);
$this->assertArrayHasKey('timestamp', $responseUpdate['data']);
- $this->assertCount(4, $responseUpdate['data']['channels']);
+ $this->assertCount(5, $responseUpdate['data']['channels']);
$this->assertContains('console', $responseUpdate['data']['channels']);
+ $this->assertContains("projects.{$this->getProject()['$id']}", $response['data']['channels']);
$this->assertContains('executions', $responseUpdate['data']['channels']);
$this->assertContains("executions.{$executionId}", $responseUpdate['data']['channels']);
$this->assertContains("functions.{$functionId}", $responseUpdate['data']['channels']);
diff --git a/tests/e2e/Services/Storage/StorageCustomClientTest.php b/tests/e2e/Services/Storage/StorageCustomClientTest.php
index 55340ab849..c723fba50a 100644
--- a/tests/e2e/Services/Storage/StorageCustomClientTest.php
+++ b/tests/e2e/Services/Storage/StorageCustomClientTest.php
@@ -1089,7 +1089,7 @@ class StorageCustomClientTest extends Scope
$this->assertEquals(200, $file['headers']['status-code']);
- // Team 2 view success
+ // Team 1 view success
$file = $this->client->call(Client::METHOD_GET, '/storage/buckets/' . $bucketId . '/files/' . $fileId . '/view', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
@@ -1112,8 +1112,6 @@ class StorageCustomClientTest extends Scope
'file' => new CURLFile(realpath(__DIR__ . '/../../../resources/logo.png'), 'image/png', 'permissions.png'),
]);
- $this->assertEquals($file['headers']['status-code'], 401);
-
// Team 2 create failure
$file = $this->client->call(Client::METHOD_POST, '/storage/buckets/' . $bucketId . '/files', [
'content-type' => 'multipart/form-data',
diff --git a/tests/e2e/Services/Teams/TeamsBaseClient.php b/tests/e2e/Services/Teams/TeamsBaseClient.php
index 03dffac6aa..8a1fed028e 100644
--- a/tests/e2e/Services/Teams/TeamsBaseClient.php
+++ b/tests/e2e/Services/Teams/TeamsBaseClient.php
@@ -161,7 +161,7 @@ trait TeamsBaseClient
$this->assertNotEmpty($response['body']['userEmail']);
$this->assertNotEmpty($response['body']['teamId']);
$this->assertNotEmpty($response['body']['teamName']);
- $this->assertCount(2, $response['body']['roles']);
+ $this->assertCount(1, $response['body']['roles']);
$this->assertEquals(false, (new DatetimeValidator())->isValid($response['body']['joined'])); // is null in DB
$this->assertEquals(false, $response['body']['confirm']);
@@ -203,7 +203,7 @@ trait TeamsBaseClient
], $this->getHeaders()), [
'email' => $email,
'name' => $name,
- 'roles' => ['admin', 'editor'],
+ 'roles' => ['developer'],
'url' => 'http://localhost:5000/join-us#title'
]);
@@ -214,7 +214,7 @@ trait TeamsBaseClient
$this->assertEquals($email, $response['body']['userEmail']);
$this->assertNotEmpty($response['body']['teamId']);
$this->assertNotEmpty($response['body']['teamName']);
- $this->assertCount(2, $response['body']['roles']);
+ $this->assertCount(1, $response['body']['roles']);
$this->assertEquals(false, (new DatetimeValidator())->isValid($response['body']['joined'])); // is null in DB
$this->assertEquals(false, $response['body']['confirm']);
@@ -255,7 +255,7 @@ trait TeamsBaseClient
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'userId' => 'abcdefdg',
- 'roles' => ['admin', 'editor'],
+ 'roles' => ['developer'],
'url' => 'http://localhost:5000/join-us#title'
]);
@@ -270,7 +270,7 @@ trait TeamsBaseClient
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'userId' => $userId,
- 'roles' => ['admin', 'editor'],
+ 'roles' => ['developer'],
'url' => 'http://localhost:5000/join-us#title'
]);
@@ -281,7 +281,7 @@ trait TeamsBaseClient
$this->assertEquals($secondEmail, $response['body']['userEmail']);
$this->assertNotEmpty($response['body']['teamId']);
$this->assertNotEmpty($response['body']['teamName']);
- $this->assertCount(2, $response['body']['roles']);
+ $this->assertCount(1, $response['body']['roles']);
$this->assertEquals(false, (new DateTimeValidator())->isValid($response['body']['joined'])); // is null in DB
$this->assertEquals(false, $response['body']['confirm']);
@@ -301,7 +301,7 @@ trait TeamsBaseClient
], $this->getHeaders()), [
'email' => $email,
'name' => 'Friend User',
- 'roles' => ['admin', 'editor'],
+ 'roles' => ['developer'],
'url' => 'http://localhost:5000/join-us#title'
]);
@@ -313,7 +313,7 @@ trait TeamsBaseClient
], $this->getHeaders()), [
'email' => 'dasdkaskdjaskdjasjkd',
'name' => $name,
- 'roles' => ['admin', 'editor'],
+ 'roles' => ['developer'],
'url' => 'http://localhost:5000/join-us#title'
]);
@@ -337,7 +337,7 @@ trait TeamsBaseClient
], $this->getHeaders()), [
'email' => $email,
'name' => $name,
- 'roles' => ['admin', 'editor'],
+ 'roles' => ['developer'],
'url' => 'http://example.com/join-us#title' // bad url
]);
@@ -413,7 +413,7 @@ trait TeamsBaseClient
$this->assertNotEmpty($response['body']['$id']);
$this->assertNotEmpty($response['body']['userId']);
$this->assertNotEmpty($response['body']['teamId']);
- $this->assertCount(2, $response['body']['roles']);
+ $this->assertCount(1, $response['body']['roles']);
$this->assertEquals(true, (new DatetimeValidator())->isValid($response['body']['joined']));
$this->assertEquals(true, $response['body']['confirm']);
$session = $response['cookies']['a_session_' . $this->getProject()['$id']];
@@ -571,7 +571,7 @@ trait TeamsBaseClient
/**
* Test for SUCCESS
*/
- $roles = ['admin', 'editor', 'uncle'];
+ $roles = ['editor', 'uncle'];
$response = $this->client->call(Client::METHOD_PATCH, '/teams/' . $teamUid . '/memberships/' . $membershipUid, array_merge([
'origin' => 'http://localhost',
'content-type' => 'application/json',
@@ -587,7 +587,6 @@ trait TeamsBaseClient
$this->assertCount(count($roles), $response['body']['roles']);
$this->assertEquals($roles[0], $response['body']['roles'][0]);
$this->assertEquals($roles[1], $response['body']['roles'][1]);
- $this->assertEquals($roles[2], $response['body']['roles'][2]);
/**
* Test for unknown team
diff --git a/tests/e2e/Services/Teams/TeamsConsoleClientTest.php b/tests/e2e/Services/Teams/TeamsConsoleClientTest.php
index 61d0f6a027..4b5ade7cbf 100644
--- a/tests/e2e/Services/Teams/TeamsConsoleClientTest.php
+++ b/tests/e2e/Services/Teams/TeamsConsoleClientTest.php
@@ -28,12 +28,13 @@ class TeamsConsoleClientTest extends Scope
// Create a user account before we create a invite so we can check if the user has permissions when it shouldn't
$user = $this->client->call(Client::METHOD_POST, '/account', [
'content-type' => 'application/json',
- 'x-appwrite-project' => 'console'], [
- 'userId' => 'unique()',
- 'email' => $email,
- 'password' => $password,
- 'name' => $name,
- ], false);
+ 'x-appwrite-project' => 'console'
+ ], [
+ 'userId' => 'unique()',
+ 'email' => $email,
+ 'password' => $password,
+ 'name' => $name,
+ ], false);
$this->assertEquals(201, $user['headers']['status-code']);
@@ -46,7 +47,7 @@ class TeamsConsoleClientTest extends Scope
], $this->getHeaders()), [
'email' => $email,
'name' => $name,
- 'roles' => ['admin', 'editor'],
+ 'roles' => ['developer'],
'url' => 'http://localhost:5000/join-us#title'
]);
@@ -76,4 +77,75 @@ class TeamsConsoleClientTest extends Scope
return $data;
}
+
+ /** @depends testUpdateTeamMembership */
+ public function testUpdateTeamMembershipRoles($data): array
+ {
+ $teamUid = $data['teamUid'] ?? '';
+ $membershipUid = $data['membershipUid'] ?? '';
+ $session = $data['session'] ?? '';
+
+ /**
+ * Test for SUCCESS
+ */
+ $roles = ['developer'];
+ $response = $this->client->call(Client::METHOD_PATCH, '/teams/' . $teamUid . '/memberships/' . $membershipUid, array_merge([
+ 'origin' => 'http://localhost',
+ 'content-type' => 'application/json',
+ 'x-appwrite-project' => $this->getProject()['$id'],
+ ], $this->getHeaders()), [
+ 'roles' => $roles
+ ]);
+
+ $this->assertEquals(200, $response['headers']['status-code']);
+ $this->assertNotEmpty($response['body']['$id']);
+ $this->assertNotEmpty($response['body']['userId']);
+ $this->assertNotEmpty($response['body']['teamId']);
+ $this->assertCount(count($roles), $response['body']['roles']);
+ $this->assertEquals($roles[0], $response['body']['roles'][0]);
+
+ /**
+ * Test for unknown team
+ */
+ $response = $this->client->call(Client::METHOD_PATCH, '/teams/' . 'abc' . '/memberships/' . $membershipUid, array_merge([
+ 'origin' => 'http://localhost',
+ 'content-type' => 'application/json',
+ 'x-appwrite-project' => $this->getProject()['$id'],
+ ], $this->getHeaders()), [
+ 'roles' => $roles
+ ]);
+
+ $this->assertEquals(404, $response['headers']['status-code']);
+
+ /**
+ * Test for unknown membership ID
+ */
+ $response = $this->client->call(Client::METHOD_PATCH, '/teams/' . $teamUid . '/memberships/' . 'abc', array_merge([
+ 'origin' => 'http://localhost',
+ 'content-type' => 'application/json',
+ 'x-appwrite-project' => $this->getProject()['$id'],
+ ], $this->getHeaders()), [
+ 'roles' => $roles
+ ]);
+
+ $this->assertEquals(404, $response['headers']['status-code']);
+
+
+ /**
+ * Test for when a user other than the owner tries to update membership
+ */
+ $response = $this->client->call(Client::METHOD_PATCH, '/teams/' . $teamUid . '/memberships/' . $membershipUid, [
+ 'origin' => 'http://localhost',
+ 'content-type' => 'application/json',
+ 'x-appwrite-project' => $this->getProject()['$id'],
+ 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
+ ], [
+ 'roles' => $roles
+ ]);
+
+ $this->assertEquals(401, $response['headers']['status-code']);
+ $this->assertEquals('User is not allowed to modify roles', $response['body']['message']);
+
+ return $data;
+ }
}
diff --git a/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php b/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php
index 54d55e5e68..d2f132e960 100644
--- a/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php
+++ b/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php
@@ -486,10 +486,11 @@ class WebhooksCustomServerTest extends Scope
/**
* Test for SUCCESS
*/
- $output = '';
+ $stderr = '';
+ $stdout = '';
$folder = 'timeout';
$code = realpath(__DIR__ . '/../../../resources/functions') . "/{$folder}/code.tar.gz";
- Console::execute('cd ' . realpath(__DIR__ . "/../../../resources/functions") . "/{$folder} && tar --exclude code.tar.gz -czf code.tar.gz .", '', $output);
+ Console::execute('cd ' . realpath(__DIR__ . "/../../../resources/functions") . "/{$folder} && tar --exclude code.tar.gz -czf code.tar.gz .", '', $stdout, $stderr);
$deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $data['functionId'] . '/deployments', array_merge([
'content-type' => 'multipart/form-data',
diff --git a/tests/extensions/Async.php b/tests/extensions/Async.php
new file mode 100644
index 0000000000..48af24bc02
--- /dev/null
+++ b/tests/extensions/Async.php
@@ -0,0 +1,17 @@
+waitMs * 1000);
+ } while (microtime(true) - $start < $this->timeoutMs / 1000);
+
+ if ($returnResult) {
+ return false;
+ }
+
+ throw $lastException;
+ }
+
+ protected function failureDescription(mixed $other): string
+ {
+ return 'the given probe was satisfied within ' . $this->timeoutMs . 'ms.';
+ }
+
+ public function toString(): string
+ {
+ return 'Eventually';
+ }
+}
diff --git a/tests/resources/docker/docker-compose.yml b/tests/resources/docker/docker-compose.yml
index 8f86fb8374..a34b4fcf88 100644
--- a/tests/resources/docker/docker-compose.yml
+++ b/tests/resources/docker/docker-compose.yml
@@ -324,7 +324,7 @@ services:
- MYSQL_USER=user
- MYSQL_PASSWORD=password
- MARIADB_AUTO_UPGRADE=1
- command: 'mysqld --innodb-flush-method=fsync --max_connections=5000'
+ command: 'mysqld --innodb-flush-method=fsync'
maildev:
image: djfarrelly/maildev
diff --git a/tests/resources/functions/php/index.php b/tests/resources/functions/php/index.php
index 76c58e87b5..27a9418b3c 100644
--- a/tests/resources/functions/php/index.php
+++ b/tests/resources/functions/php/index.php
@@ -1,6 +1,20 @@
log('body-is-' . ($context->req->body ?? ''));
+ $context->log('custom-header-is-' . ($context->req->headers['x-custom-header'] ?? ''));
+ $context->log('method-is-' . \strtolower($context->req->method ?? ''));
+ $context->log('path-is-' . ($context->req->path ?? ''));
+ $context->log('user-is-' . $context->req->headers['x-appwrite-user-id'] ?? '');
+
+ if (empty($context->req->headers['x-appwrite-user-jwt'] ?? '')) {
+ $context->log('jwt-is-invalid');
+ } else {
+ $context->log('jwt-is-valid');
+ }
+
+ $context->error('error-log-works');
+
$statusCode = $context->req->query['code'] ?? '200';
return $context->res->json([
diff --git a/tests/unit/Auth/AuthTest.php b/tests/unit/Auth/AuthTest.php
index 1ff5850402..705da42879 100644
--- a/tests/unit/Auth/AuthTest.php
+++ b/tests/unit/Auth/AuthTest.php
@@ -3,7 +3,6 @@
namespace Tests\Unit\Auth;
use Appwrite\Auth\Auth;
-use Appwrite\Auth\Authentication;
use PHPUnit\Framework\TestCase;
use Utopia\Database\DateTime;
use Utopia\Database\Document;
@@ -14,31 +13,21 @@ use Utopia\Database\Validator\Roles;
class AuthTest extends TestCase
{
- protected Authorization $auth;
- protected Authentication $authentication;
-
- public function setUp(): void
- {
- parent::setUp();
- $this->auth = new Authorization();
- $this->authentication = new Authentication();
- }
-
/**
* Reset Roles
*/
public function tearDown(): void
{
- $this->auth->cleanRoles();
- $this->auth->addRole(Role::any()->toString());
+ Authorization::cleanRoles();
+ Authorization::setRole(Role::any()->toString());
}
public function testCookieName(): void
{
$name = 'cookie-name';
- $this->assertEquals($this->authentication->setCookieName($name), $name);
- $this->assertEquals($this->authentication->getCookieName(), $name);
+ $this->assertEquals(Auth::setCookieName($name), $name);
+ $this->assertEquals(Auth::$cookieName, $name);
}
public function testEncodeDecodeSession(): void
@@ -358,7 +347,7 @@ class AuthTest extends TestCase
'$id' => ''
]);
- $roles = Auth::getRoles($user, $this->auth);
+ $roles = Auth::getRoles($user);
$this->assertCount(1, $roles);
$this->assertContains(Role::guests()->toString(), $roles);
}
@@ -394,7 +383,7 @@ class AuthTest extends TestCase
]
]);
- $roles = Auth::getRoles($user, $this->auth);
+ $roles = Auth::getRoles($user);
$this->assertCount(13, $roles);
$this->assertContains(Role::users()->toString(), $roles);
@@ -415,21 +404,21 @@ class AuthTest extends TestCase
$user['emailVerification'] = false;
$user['phoneVerification'] = false;
- $roles = Auth::getRoles($user, $this->auth);
+ $roles = Auth::getRoles($user);
$this->assertContains(Role::users(Roles::DIMENSION_UNVERIFIED)->toString(), $roles);
$this->assertContains(Role::user(ID::custom('123'), Roles::DIMENSION_UNVERIFIED)->toString(), $roles);
// Enable single verification type
$user['emailVerification'] = true;
- $roles = Auth::getRoles($user, $this->auth);
+ $roles = Auth::getRoles($user);
$this->assertContains(Role::users(Roles::DIMENSION_VERIFIED)->toString(), $roles);
$this->assertContains(Role::user(ID::custom('123'), Roles::DIMENSION_VERIFIED)->toString(), $roles);
}
public function testPrivilegedUserRoles(): void
{
- $this->auth->addRole(Auth::USER_ROLE_OWNER);
+ Authorization::setRole(Auth::USER_ROLE_OWNER);
$user = new Document([
'$id' => ID::custom('123'),
'emailVerification' => true,
@@ -455,7 +444,7 @@ class AuthTest extends TestCase
]
]);
- $roles = Auth::getRoles($user, $this->auth);
+ $roles = Auth::getRoles($user);
$this->assertCount(7, $roles);
$this->assertNotContains(Role::users()->toString(), $roles);
@@ -473,7 +462,7 @@ class AuthTest extends TestCase
public function testAppUserRoles(): void
{
- $this->auth->addRole(Auth::USER_ROLE_APPS);
+ Authorization::setRole(Auth::USER_ROLE_APPS);
$user = new Document([
'$id' => ID::custom('123'),
'memberships' => [
@@ -497,7 +486,7 @@ class AuthTest extends TestCase
]
]);
- $roles = Auth::getRoles($user, $this->auth);
+ $roles = Auth::getRoles($user);
$this->assertCount(7, $roles);
$this->assertNotContains(Role::users()->toString(), $roles);
diff --git a/tests/unit/Event/EventTest.php b/tests/unit/Event/EventTest.php
index 38534b9b16..dd9833378f 100644
--- a/tests/unit/Event/EventTest.php
+++ b/tests/unit/Event/EventTest.php
@@ -66,13 +66,9 @@ class EventTest extends TestCase
$this->assertEquals('eventValue1', $this->object->getParam('eventKey1'));
$this->assertEquals('eventValue2', $this->object->getParam('eventKey2'));
$this->assertEquals(null, $this->object->getParam('eventKey3'));
-
- global $registry;
- $pools = $registry->get('pools');
- $dsn = $pools['pools-queue-queue']['dsn'];
- $queue = new Queue\Connection\Redis($dsn->getHost(), $dsn->getPort());
-
- $client = new Client($this->object->getQueue(), $queue);
+ global $register;
+ $pools = $register->get('pools');
+ $client = new Client($this->object->getQueue(), $pools->get('queue')->pop()->getResource());
$this->assertEquals($client->getQueueSize(), 1);
}
diff --git a/tests/unit/GraphQL/BuilderTest.php b/tests/unit/GraphQL/BuilderTest.php
index f11045f318..d79a104c90 100644
--- a/tests/unit/GraphQL/BuilderTest.php
+++ b/tests/unit/GraphQL/BuilderTest.php
@@ -4,10 +4,8 @@ namespace Tests\Unit\GraphQL;
use Appwrite\GraphQL\Types\Mapper;
use Appwrite\Utopia\Response;
-use Appwrite\Utopia\Response\Models;
use PHPUnit\Framework\TestCase;
use Swoole\Http\Response as SwooleResponse;
-use Utopia\Http\Adapter\Swoole\Response as UtopiaSwooleResponse;
class BuilderTest extends TestCase
{
@@ -15,9 +13,8 @@ class BuilderTest extends TestCase
public function setUp(): void
{
- Models::init();
- $this->response = new Response(new UtopiaSwooleResponse(new SwooleResponse()));
- Mapper::init(Models::getModels());
+ $this->response = new Response(new SwooleResponse());
+ Mapper::init($this->response->getModels());
}
/**
@@ -25,7 +22,7 @@ class BuilderTest extends TestCase
*/
public function testCreateTypeMapping()
{
- $model = Models::getModel(Response::MODEL_COLLECTION);
+ $model = $this->response->getModel(Response::MODEL_COLLECTION);
$type = Mapper::model(\ucfirst($model->getType()));
$this->assertEquals('Collection', $type->name);
}
diff --git a/tests/unit/Messaging/MessagingChannelsTest.php b/tests/unit/Messaging/MessagingChannelsTest.php
index 77b3cee7d6..8ba0374093 100644
--- a/tests/unit/Messaging/MessagingChannelsTest.php
+++ b/tests/unit/Messaging/MessagingChannelsTest.php
@@ -8,7 +8,6 @@ use PHPUnit\Framework\TestCase;
use Utopia\Database\Document;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Role;
-use Utopia\Database\Validator\Authorization as ValidatorAuthorization;
class MessagingChannelsTest extends TestCase
{
@@ -35,12 +34,8 @@ class MessagingChannelsTest extends TestCase
'functions.1',
];
- protected ValidatorAuthorization $auth;
-
public function setUp(): void
{
- $this->auth = new ValidatorAuthorization();
-
/**
* Setup global Counts
*/
@@ -71,7 +66,7 @@ class MessagingChannelsTest extends TestCase
]
]);
- $roles = Auth::getRoles($user, $this->auth);
+ $roles = Auth::getRoles($user);
$parsedChannels = Realtime::convertChannels([0 => $channel], $user->getId());
@@ -95,7 +90,7 @@ class MessagingChannelsTest extends TestCase
'$id' => ''
]);
- $roles = Auth::getRoles($user, $this->auth);
+ $roles = Auth::getRoles($user);
$parsedChannels = Realtime::convertChannels([0 => $channel], $user->getId());
diff --git a/tests/unit/Migration/MigrationTest.php b/tests/unit/Migration/MigrationTest.php
index 8043ec9f94..536278d55b 100644
--- a/tests/unit/Migration/MigrationTest.php
+++ b/tests/unit/Migration/MigrationTest.php
@@ -36,7 +36,7 @@ abstract class MigrationTest extends TestCase
*/
public function testMigrationVersions(): void
{
- //require_once __DIR__ . '/../../../app/init.php';
+ require_once __DIR__ . '/../../../app/init.php';
foreach (Migration::$versions as $class) {
$this->assertTrue(class_exists('Appwrite\\Migration\\Version\\' . $class));
diff --git a/tests/unit/Utopia/RequestTest.php b/tests/unit/Utopia/RequestTest.php
index 6124a7a0c1..73daaa88bc 100644
--- a/tests/unit/Utopia/RequestTest.php
+++ b/tests/unit/Utopia/RequestTest.php
@@ -7,8 +7,7 @@ use PHPUnit\Framework\TestCase;
use Swoole\Http\Request as SwooleRequest;
use Tests\Unit\Utopia\Request\Filters\First;
use Tests\Unit\Utopia\Request\Filters\Second;
-use Utopia\Http\Adapter\Swoole\Request as UtopiaSwooleRequest;
-use Utopia\Http\Route;
+use Utopia\Route;
class RequestTest extends TestCase
{
@@ -16,7 +15,7 @@ class RequestTest extends TestCase
public function setUp(): void
{
- $this->request = new Request(new UtopiaSwooleRequest(new SwooleRequest()));
+ $this->request = new Request(new SwooleRequest());
}
public function testFilters(): void
@@ -37,7 +36,7 @@ class RequestTest extends TestCase
// set test header to prevent header populaten inside the request class
$this->request->addHeader('EXAMPLE', 'VALUE');
$this->request->setRoute($route);
- $this->request->setQuery([
+ $this->request->setQueryString([
'initial' => true,
'first' => false
]);
diff --git a/tests/unit/Utopia/ResponseTest.php b/tests/unit/Utopia/ResponseTest.php
index 1cd02beb2c..452119fafb 100644
--- a/tests/unit/Utopia/ResponseTest.php
+++ b/tests/unit/Utopia/ResponseTest.php
@@ -3,14 +3,12 @@
namespace Tests\Unit\Utopia;
use Appwrite\Utopia\Response;
-use Appwrite\Utopia\Response\Models;
use Exception;
use PHPUnit\Framework\TestCase;
use Swoole\Http\Response as SwooleResponse;
use Tests\Unit\Utopia\Response\Filters\First;
use Tests\Unit\Utopia\Response\Filters\Second;
use Utopia\Database\Document;
-use Utopia\Http\Adapter\Swoole\Response as UtopiaSwooleResponse;
class ResponseTest extends TestCase
{
@@ -18,10 +16,10 @@ class ResponseTest extends TestCase
public function setUp(): void
{
- $this->response = new Response(new UtopiaSwooleResponse(new SwooleResponse()));
- Models::setModel(new Single());
- Models::setModel(new Lists());
- Models::setModel(new Nested());
+ $this->response = new Response(new SwooleResponse());
+ $this->response->setModel(new Single());
+ $this->response->setModel(new Lists());
+ $this->response->setModel(new Nested());
}
public function testFilters(): void
@@ -57,12 +55,32 @@ class ResponseTest extends TestCase
'integer' => 123,
'boolean' => true,
'hidden' => 'secret',
+ 'array' => [
+ 'string 1',
+ 'string 2'
+ ],
]), 'single');
$this->assertArrayHasKey('string', $output);
$this->assertArrayHasKey('integer', $output);
$this->assertArrayHasKey('boolean', $output);
$this->assertArrayNotHasKey('hidden', $output);
+ $this->assertIsArray($output['array']);
+
+ // test optional array
+ $output = $this->response->output(new Document([
+ 'string' => 'lorem ipsum',
+ 'integer' => 123,
+ 'boolean' => true,
+ 'hidden' => 'secret',
+ ]), 'single');
+ $this->assertArrayHasKey('string', $output);
+ $this->assertArrayHasKey('integer', $output);
+ $this->assertArrayHasKey('boolean', $output);
+ $this->assertArrayNotHasKey('hidden', $output);
+ $this->assertArrayHasKey('array', $output);
+ $this->assertNull($output['array']);
+
}
public function testResponseModelRequired(): void
diff --git a/tests/unit/Utopia/Single.php b/tests/unit/Utopia/Single.php
index 3bd09ef6da..b7f36d10a8 100644
--- a/tests/unit/Utopia/Single.php
+++ b/tests/unit/Utopia/Single.php
@@ -28,6 +28,11 @@ class Single extends Model
'type' => self::TYPE_STRING,
'default' => 'default',
'required' => true
+ ])
+ ->addRule('array', [
+ 'type' => self::TYPE_STRING,
+ 'required' => false,
+ 'array' => true,
]);
}