mirror of
https://github.com/appwrite/appwrite
synced 2026-05-24 09:28:40 +00:00
Merge remote-tracking branch 'origin/1.6.x' into feat-migration
# Conflicts: # Dockerfile # composer.json # composer.lock # src/Appwrite/Platform/Workers/Deletes.php
This commit is contained in:
commit
4543c182b5
5163 changed files with 395278 additions and 2944 deletions
13
.env
13
.env
|
|
@ -4,11 +4,13 @@ _APP_LOCALE=en
|
|||
_APP_WORKER_PER_CORE=6
|
||||
_APP_CONSOLE_WHITELIST_ROOT=disabled
|
||||
_APP_CONSOLE_WHITELIST_EMAILS=
|
||||
_APP_CONSOLE_SESSION_ALERTS=enabled
|
||||
_APP_CONSOLE_WHITELIST_IPS=
|
||||
_APP_CONSOLE_COUNTRIES_DENYLIST=AQ
|
||||
_APP_CONSOLE_HOSTNAMES=localhost,appwrite.io,*.appwrite.io
|
||||
_APP_SYSTEM_EMAIL_NAME=Appwrite
|
||||
_APP_SYSTEM_EMAIL_ADDRESS=team@appwrite.io
|
||||
_APP_SYSTEM_EMAIL_ADDRESS=noreply@appwrite.io
|
||||
_APP_SYSTEM_TEAM_EMAIL=team@appwrite.io
|
||||
_APP_EMAIL_SECURITY=security@appwrite.io
|
||||
_APP_EMAIL_CERTIFICATES=certificates@appwrite.io
|
||||
_APP_SYSTEM_RESPONSE_FORMAT=
|
||||
|
|
@ -17,7 +19,7 @@ _APP_OPTIONS_ROUTER_PROTECTION=disabled
|
|||
_APP_OPTIONS_FORCE_HTTPS=disabled
|
||||
_APP_OPTIONS_FUNCTIONS_FORCE_HTTPS=disabled
|
||||
_APP_OPENSSL_KEY_V1=your-secret-key
|
||||
_APP_DOMAIN=localhost
|
||||
_APP_DOMAIN=traefik
|
||||
_APP_DOMAIN_FUNCTIONS=functions.localhost
|
||||
_APP_DOMAIN_TARGET=localhost
|
||||
_APP_REDIS_HOST=redis
|
||||
|
|
@ -67,8 +69,8 @@ _APP_STORAGE_PREVIEW_LIMIT=20000000
|
|||
_APP_FUNCTIONS_SIZE_LIMIT=30000000
|
||||
_APP_FUNCTIONS_TIMEOUT=900
|
||||
_APP_FUNCTIONS_BUILD_TIMEOUT=900
|
||||
_APP_FUNCTIONS_CPUS=1
|
||||
_APP_FUNCTIONS_MEMORY=1024
|
||||
_APP_FUNCTIONS_CPUS=8
|
||||
_APP_FUNCTIONS_MEMORY=8192
|
||||
_APP_FUNCTIONS_INACTIVE_THRESHOLD=600
|
||||
_APP_FUNCTIONS_MAINTENANCE_INTERVAL=600
|
||||
_APP_FUNCTIONS_RUNTIMES_NETWORK=runtimes
|
||||
|
|
@ -85,7 +87,6 @@ _APP_USAGE_AGGREGATION_INTERVAL=30
|
|||
_APP_MAINTENANCE_RETENTION_USAGE_HOURLY=8640000
|
||||
_APP_MAINTENANCE_RETENTION_SCHEDULES=86400
|
||||
_APP_USAGE_STATS=enabled
|
||||
_APP_LOGGING_PROVIDER=
|
||||
_APP_LOGGING_CONFIG=
|
||||
_APP_GRAPHQL_MAX_BATCH_SIZE=10
|
||||
_APP_GRAPHQL_MAX_COMPLEXITY=250
|
||||
|
|
@ -105,4 +106,4 @@ _APP_MESSAGE_SMS_TEST_DSN=
|
|||
_APP_MESSAGE_EMAIL_TEST_DSN=
|
||||
_APP_MESSAGE_PUSH_TEST_DSN=
|
||||
_APP_WEBHOOK_MAX_FAILED_ATTEMPTS=10
|
||||
_APP_PROJECT_REGIONS=default
|
||||
_APP_PROJECT_REGIONS=default
|
||||
|
|
|
|||
47
.github/workflows/nightly.yml
vendored
Normal file
47
.github/workflows/nightly.yml
vendored
Normal file
|
|
@ -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'
|
||||
42
.github/workflows/pr-scan.yml
vendored
42
.github/workflows/pr-scan.yml
vendored
|
|
@ -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
|
||||
|
|
|
|||
101
.github/workflows/tests.yml
vendored
101
.github/workflows/tests.yml
vendored
|
|
@ -16,15 +16,15 @@ 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
|
||||
|
|
@ -39,7 +39,7 @@ jobs:
|
|||
VERSION=dev
|
||||
|
||||
- name: Cache Docker Image
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
key: ${{ env.CACHE_KEY }}
|
||||
path: /tmp/${{ env.IMAGE }}.tar
|
||||
|
|
@ -51,10 +51,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 +81,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 +113,7 @@ jobs:
|
|||
Console,
|
||||
Databases,
|
||||
Functions,
|
||||
FunctionsSchedule,
|
||||
GraphQL,
|
||||
Health,
|
||||
Locale,
|
||||
|
|
@ -128,10 +129,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,10 +142,88 @@ 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
|
||||
|
||||
- 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
|
||||
|
||||
benchmarking:
|
||||
name: Benchmark
|
||||
runs-on: ubuntu-latest
|
||||
needs: setup
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
- name: Load Cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
key: ${{ env.CACHE_KEY }}
|
||||
path: /tmp/${{ env.IMAGE }}.tar
|
||||
fail-on-cache-miss: true
|
||||
- name: Load and Start Appwrite
|
||||
run: |
|
||||
sed -i 's/traefik/localhost/g' .env
|
||||
docker load --input /tmp/${{ env.IMAGE }}.tar
|
||||
docker compose up -d
|
||||
sleep 10
|
||||
- name: Install Oha
|
||||
run: |
|
||||
echo "deb [signed-by=/usr/share/keyrings/azlux-archive-keyring.gpg] http://packages.azlux.fr/debian/ stable main" | sudo tee /etc/apt/sources.list.d/azlux.list
|
||||
sudo wget -O /usr/share/keyrings/azlux-archive-keyring.gpg https://azlux.fr/repo.gpg
|
||||
sudo apt update
|
||||
sudo apt install oha
|
||||
- name: Benchmark PR
|
||||
run: oha -z 180s http://localhost/v1/health/version -j > benchmark.json
|
||||
- name: Cleaning
|
||||
run: docker compose down -v
|
||||
- name: Installing latest version
|
||||
run: |
|
||||
rm docker-compose.yml
|
||||
rm .env
|
||||
curl https://appwrite.io/install/compose -o docker-compose.yml
|
||||
curl https://appwrite.io/install/env -o .env
|
||||
sed -i 's/_APP_OPTIONS_ABUSE=enabled/_APP_OPTIONS_ABUSE=disabled/g' .env
|
||||
docker compose up -d
|
||||
sleep 10
|
||||
- name: Benchmark Latest
|
||||
run: oha -z 180s http://localhost/v1/health/version -j > benchmark-latest.json
|
||||
- name: Prepare comment
|
||||
run: |
|
||||
echo '## :sparkles: Benchmark results' > benchmark.txt
|
||||
echo ' ' >> benchmark.txt
|
||||
echo "- Requests per second: $(jq -r '.summary.requestsPerSec|tonumber?|floor|tostring|[while(length>0;.[:-3])|.[-3:]]|reverse|join(",")' benchmark.json)" >> benchmark.txt
|
||||
echo "- Requests with 200 status code: $(jq -r '.statusCodeDistribution."200"|tostring|[while(length>0;.[:-3])|.[-3:]]|reverse|join(",")' benchmark.json)" >> benchmark.txt
|
||||
echo "- P99 latency: $(jq -r '.latencyPercentiles.p99' benchmark.json )" >> benchmark.txt
|
||||
echo " " >> benchmark.txt
|
||||
echo " " >> benchmark.txt
|
||||
echo "## :zap: Benchmark Comparison" >> benchmark.txt
|
||||
echo " " >> benchmark.txt
|
||||
echo "| Metric | This PR | Latest version | " >> benchmark.txt
|
||||
echo "| --- | --- | --- | " >> benchmark.txt
|
||||
echo "| RPS | $(jq -r '.summary.requestsPerSec|tonumber?|floor|tostring|[while(length>0;.[:-3])|.[-3:]]|reverse|join(",")' benchmark.json) | $(jq -r '.summary.requestsPerSec|tonumber|floor|tostring|[while(length>0;.[:-3])|.[-3:]]|reverse|join(",")' benchmark-latest.json) | " >> benchmark.txt
|
||||
echo "| 200 | $(jq -r '.statusCodeDistribution."200"|tostring|[while(length>0;.[:-3])|.[-3:]]|reverse|join(",")' benchmark.json) | $(jq -r '.statusCodeDistribution."200"|tostring|[while(length>0;.[:-3])|.[-3:]]|reverse|join(",")' benchmark-latest.json) | " >> benchmark.txt
|
||||
echo "| P99 | $(jq -r '.latencyPercentiles.p99' benchmark.json ) | $(jq -r '.latencyPercentiles.p99' benchmark-latest.json ) | " >> benchmark.txt
|
||||
- name: Save results
|
||||
uses: actions/upload-artifact@v4
|
||||
if: ${{ !cancelled() }}
|
||||
with:
|
||||
name: benchmark.json
|
||||
path: benchmark.json
|
||||
retention-days: 7
|
||||
- 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: Benchmark results
|
||||
- name: Comment on PR
|
||||
uses: peter-evans/create-or-update-comment@v4
|
||||
with:
|
||||
comment-id: ${{ steps.fc.outputs.comment-id }}
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
body-path: benchmark.txt
|
||||
edit-mode: replace
|
||||
|
|
|
|||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -9,6 +9,7 @@
|
|||
!/.idea/php.xml
|
||||
.DS_Store
|
||||
.php_cs.cache
|
||||
.phpactor.json
|
||||
debug/
|
||||
app/sdks
|
||||
dev/yasd_init.php
|
||||
|
|
|
|||
4
.gitmodules
vendored
4
.gitmodules
vendored
|
|
@ -1,4 +0,0 @@
|
|||
[submodule "app/console"]
|
||||
path = app/console
|
||||
url = https://github.com/appwrite/console
|
||||
branch = 4.3.30
|
||||
142
CHANGES.md
142
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
|
||||
|
|
|
|||
|
|
@ -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) |
|
||||
|
|
@ -553,6 +556,12 @@ To run end-2-end tests for a specific service use:
|
|||
docker compose exec appwrite test /usr/src/code/tests/e2e/Services/[ServiceName]
|
||||
```
|
||||
|
||||
To run one specific test:
|
||||
|
||||
```bash
|
||||
docker compose exec appwrite vendor/bin/phpunit --filter [FunctionName]
|
||||
```
|
||||
|
||||
## Benchmarking
|
||||
|
||||
You can use WRK Docker image to benchmark the server performance. Benchmarking is extremely useful when you want to compare how the server behaves before and after a change has been applied. Replace [APPWRITE_HOSTNAME_OR_IP] with your Appwrite server hostname or IP. Note that localhost is not accessible from inside the WRK container.
|
||||
|
|
|
|||
23
Dockerfile
23
Dockerfile
|
|
@ -12,24 +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 --platform=$BUILDPLATFORM node:20.11.0-alpine3.19 AS node
|
||||
|
||||
COPY app/console /usr/local/src/console
|
||||
|
||||
WORKDIR /usr/local/src/console
|
||||
|
||||
ARG VITE_GA_PROJECT
|
||||
ARG VITE_CONSOLE_MODE
|
||||
ARG VITE_APPWRITE_GROWTH_ENDPOINT=https://growth.appwrite.io/v1
|
||||
|
||||
ENV VITE_GA_PROJECT=$VITE_GA_PROJECT
|
||||
ENV VITE_CONSOLE_MODE=$VITE_CONSOLE_MODE
|
||||
ENV VITE_APPWRITE_GROWTH_ENDPOINT=$VITE_APPWRITE_GROWTH_ENDPOINT
|
||||
|
||||
RUN npm ci
|
||||
RUN npm run build
|
||||
|
||||
FROM appwrite/base:0.9.1 AS final
|
||||
FROM appwrite/base:0.9.3 AS final
|
||||
|
||||
LABEL maintainer="team@appwrite.io"
|
||||
|
||||
|
|
@ -45,10 +28,11 @@ 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
|
||||
COPY --from=node /usr/local/src/console/build /usr/src/code/console
|
||||
|
||||
# Add Source Code
|
||||
COPY ./app /usr/src/code/app
|
||||
|
|
@ -79,6 +63,7 @@ RUN chmod +x /usr/local/bin/doctor && \
|
|||
chmod +x /usr/local/bin/migrate && \
|
||||
chmod +x /usr/local/bin/realtime && \
|
||||
chmod +x /usr/local/bin/schedule-functions && \
|
||||
chmod +x /usr/local/bin/schedule-executions && \
|
||||
chmod +x /usr/local/bin/schedule-messages && \
|
||||
chmod +x /usr/local/bin/sdks && \
|
||||
chmod +x /usr/local/bin/specs && \
|
||||
|
|
|
|||
|
|
@ -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 的本机主机上完成安装后,服务器可能需要几分钟才能启动。
|
||||
|
|
|
|||
14
README.md
14
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) 🚀
|
||||
|
||||
<br />
|
||||
<p align="center">
|
||||
|
|
@ -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:
|
|||
<br /><sub><b>Akamai Compute</b></sub></a>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" width="100" height="100">
|
||||
<a href="https://aws.amazon.com/marketplace/pp/prodview-2hiaeo2px4md6">
|
||||
<img width="50" height="39" src="public/images/integrations/aws-logo.svg" alt="AWS Logo" />
|
||||
<br /><sub><b>AWS Marketplace</b></sub></a>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
|
|
|||
Binary file not shown.
BIN
app/assets/dbip/dbip-country-lite-2024-09.mmdb
Normal file
BIN
app/assets/dbip/dbip-country-lite-2024-09.mmdb
Normal file
Binary file not shown.
16
app/cli.php
16
app/cli.php
|
|
@ -1,12 +1,12 @@
|
|||
<?php
|
||||
|
||||
require_once __DIR__ . '/init.php';
|
||||
require_once __DIR__ . '/controllers/general.php';
|
||||
|
||||
use Appwrite\Event\Certificate;
|
||||
use Appwrite\Event\Delete;
|
||||
use Appwrite\Event\Func;
|
||||
use Appwrite\Platform\Appwrite;
|
||||
use Appwrite\Runtimes\Runtimes;
|
||||
use Utopia\Cache\Adapter\Sharding;
|
||||
use Utopia\Cache\Cache;
|
||||
use Utopia\CLI\CLI;
|
||||
|
|
@ -23,6 +23,12 @@ use Utopia\Queue\Connection;
|
|||
use Utopia\Registry\Registry;
|
||||
use Utopia\System\System;
|
||||
|
||||
// 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';
|
||||
|
||||
Authorization::disable();
|
||||
|
||||
CLI::setResource('register', fn () => $register);
|
||||
|
|
@ -192,8 +198,12 @@ CLI::setResource('logError', function (Registry $register) {
|
|||
$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::warning("Failed: {$error->getMessage()}");
|
||||
|
|
|
|||
|
|
@ -2406,6 +2406,17 @@ $projectCollections = array_merge([
|
|||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('originalId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'signed' => true,
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'format' => '',
|
||||
'filters' => [],
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
],
|
||||
],
|
||||
'indexes' => [
|
||||
[
|
||||
|
|
@ -3029,7 +3040,7 @@ $projectCollections = array_merge([
|
|||
'size' => 8,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => 'v3',
|
||||
'default' => 'v4',
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
|
|
@ -3054,7 +3065,29 @@ $projectCollections = array_merge([
|
|||
'required' => false,
|
||||
'default' => null,
|
||||
'filters' => [],
|
||||
]
|
||||
],
|
||||
[
|
||||
'array' => false,
|
||||
'$id' => ID::custom('specification'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 128,
|
||||
'signed' => false,
|
||||
'required' => false,
|
||||
'default' => APP_FUNCTION_SPECIFICATION_DEFAULT,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('scopes'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => [],
|
||||
'array' => true,
|
||||
'filters' => [],
|
||||
],
|
||||
],
|
||||
'indexes' => [
|
||||
[
|
||||
|
|
@ -3837,6 +3870,39 @@ $projectCollections = array_merge([
|
|||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('scheduledAt'),
|
||||
'type' => Database::VAR_DATETIME,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => false,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => ['datetime'],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('scheduleInternalId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('scheduleId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
],
|
||||
'indexes' => [
|
||||
[
|
||||
|
|
@ -3867,6 +3933,27 @@ $projectCollections = array_merge([
|
|||
'lengths' => [32],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_requestMethod'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['requestMethod'],
|
||||
'lengths' => [128],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_requestPath'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['requestPath'],
|
||||
'lengths' => [Database::LENGTH_KEY],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_deployment'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['deploymentId'],
|
||||
'lengths' => [Database::LENGTH_KEY],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_responseStatusCode'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
|
|
@ -4033,13 +4120,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,
|
||||
|
|
@ -4311,6 +4409,17 @@ $consoleCollections = array_merge([
|
|||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'accessedAt',
|
||||
'type' => Database::VAR_DATETIME,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => false,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => ['datetime'],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('services'),
|
||||
'type' => Database::VAR_STRING,
|
||||
|
|
@ -4421,6 +4530,28 @@ $consoleCollections = array_merge([
|
|||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('pingCount'),
|
||||
'type' => Database::VAR_INTEGER,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => false,
|
||||
'required' => false,
|
||||
'default' => 0,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('pingedAt'),
|
||||
'type' => Database::VAR_DATETIME,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => false,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => ['datetime'],
|
||||
]
|
||||
],
|
||||
'indexes' => [
|
||||
[
|
||||
|
|
@ -4518,6 +4649,17 @@ $consoleCollections = array_merge([
|
|||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('data'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 65535,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => new \stdClass(),
|
||||
'array' => false,
|
||||
'filters' => ['json', 'encrypt'],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('active'),
|
||||
'type' => Database::VAR_BOOLEAN,
|
||||
|
|
@ -4590,7 +4732,7 @@ $consoleCollections = array_merge([
|
|||
'$id' => ID::custom('type'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 16,
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
|
|
@ -5685,7 +5827,7 @@ $bucketCollections = [
|
|||
'$id' => ID::custom('metadata'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 16384, // https://tools.ietf.org/html/rfc4288#section-4.2
|
||||
'size' => 75000, // https://tools.ietf.org/html/rfc4288#section-4.2
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
|
|
|
|||
|
|
@ -332,6 +332,11 @@ return [
|
|||
'description' => 'API key and session used in the same request. Use either `setSession` or `setKey`. Learn about which authentication method to use in the SSR docs: https://appwrite.io/docs/products/auth/server-side-rendering',
|
||||
'code' => 403,
|
||||
],
|
||||
Exception::API_KEY_EXPIRED => [
|
||||
'name' => Exception::API_KEY_EXPIRED,
|
||||
'description' => 'The dynamic API key has expired. Please don\'t use dynamic API keys for more than duration of the execution.',
|
||||
'code' => 401,
|
||||
],
|
||||
|
||||
/** Teams */
|
||||
Exception::TEAM_NOT_FOUND => [
|
||||
|
|
@ -524,6 +529,11 @@ return [
|
|||
'description' => 'Synchronous function execution timed out. Use asynchronous execution instead, or ensure the execution duration doesn\'t exceed 30 seconds.',
|
||||
'code' => 408,
|
||||
],
|
||||
Exception::FUNCTION_TEMPLATE_NOT_FOUND => [
|
||||
'name' => Exception::FUNCTION_TEMPLATE_NOT_FOUND,
|
||||
'description' => 'Function Template with the requested ID could not be found.',
|
||||
'code' => 404,
|
||||
],
|
||||
|
||||
/** Builds */
|
||||
Exception::BUILD_NOT_FOUND => [
|
||||
|
|
@ -541,6 +551,11 @@ return [
|
|||
'description' => 'Build with the requested ID is already in progress. Please wait before you can retry.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::BUILD_ALREADY_COMPLETED => [
|
||||
'name' => Exception::BUILD_ALREADY_COMPLETED,
|
||||
'description' => 'Build with the requested ID is already completed and cannot be canceled.',
|
||||
'code' => 400,
|
||||
],
|
||||
|
||||
/** Deployments */
|
||||
Exception::DEPLOYMENT_NOT_FOUND => [
|
||||
|
|
@ -556,6 +571,12 @@ return [
|
|||
'code' => 404,
|
||||
],
|
||||
|
||||
Exception::EXECUTION_IN_PROGRESS => [
|
||||
'name' => Exception::EXECUTION_IN_PROGRESS,
|
||||
'description' => 'Can\'t delete ongoing execution. Please wait for execution to finish before deleting it.',
|
||||
'code' => 400,
|
||||
],
|
||||
|
||||
/** Databases */
|
||||
Exception::DATABASE_NOT_FOUND => [
|
||||
'name' => Exception::DATABASE_NOT_FOUND,
|
||||
|
|
@ -678,6 +699,11 @@ return [
|
|||
'description' => 'The relationship value is invalid.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::ATTRIBUTE_INVALID_RESIZE => [
|
||||
'name' => Exception::ATTRIBUTE_INVALID_RESIZE,
|
||||
'description' => "Existing data is too large for new size, truncate your existing data then try again.",
|
||||
'code' => 400,
|
||||
],
|
||||
|
||||
/** Indexes */
|
||||
Exception::INDEX_NOT_FOUND => [
|
||||
|
|
|
|||
2073
app/config/function-templates.php
Normal file
2073
app/config/function-templates.php
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -6,7 +6,9 @@ return [
|
|||
'magicSession',
|
||||
'recovery',
|
||||
'invitation',
|
||||
'mfaChallenge'
|
||||
'mfaChallenge',
|
||||
'sessionAlert',
|
||||
'otpSession'
|
||||
],
|
||||
'sms' => [
|
||||
'verification',
|
||||
|
|
|
|||
14
app/config/locale/templates/email-session-alert.tpl
Normal file
14
app/config/locale/templates/email-session-alert.tpl
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<p>{{hello}},</p>
|
||||
|
||||
<p>{{body}}</p>
|
||||
|
||||
<ol>
|
||||
<li>{{listDevice}}</li>
|
||||
<li>{{listIpAddress}}</li>
|
||||
<li>{{listCountry}}</li>
|
||||
</ol>
|
||||
|
||||
<p>{{footer}}</p>
|
||||
|
||||
<p style="margin-bottom: 0px;">{{thanks}}</p>
|
||||
<p style="margin-top: 0px;">{{signature}}</p>
|
||||
|
|
@ -18,6 +18,15 @@
|
|||
"emails.magicSession.securityPhrase": "Security phrase for this email is {{b}}{{phrase}}{{/b}}. You can trust this email if this phrase matches the phrase shown during sign in.",
|
||||
"emails.magicSession.thanks": "Thanks,",
|
||||
"emails.magicSession.signature": "{{project}} team",
|
||||
"emails.sessionAlert.subject": "Security alert: new session on your {{project}} account",
|
||||
"emails.sessionAlert.hello":"Hello {{user}}",
|
||||
"emails.sessionAlert.body": "A new session has been created on your {{b}}{{project}}{{/b}} account, {{b}}on {{date}}, {{year}} at {{time}} UTC{{/b}}.\nHere are the details of the new session: ",
|
||||
"emails.sessionAlert.listDevice": "Device: {{b}}{{device}}{{/b}}",
|
||||
"emails.sessionAlert.listIpAddress": "IP Address: {{b}}{{ipAddress}}{{/b}}",
|
||||
"emails.sessionAlert.listCountry": "Country: {{b}}{{country}}{{/b}}",
|
||||
"emails.sessionAlert.footer": "If this was you, there's nothing more you need to do.\nIf you didn't initiate this session or suspect any unauthorized activity, please secure your account.",
|
||||
"emails.sessionAlert.thanks": "Thanks,",
|
||||
"emails.sessionAlert.signature": "{{project}} team",
|
||||
"emails.otpSession.subject": "OTP for {{project}} Login",
|
||||
"emails.otpSession.hello": "Hello {{user}}",
|
||||
"emails.otpSession.description": "Enter the following verification code when prompted to securely sign in to your {{b}}{{project}}{{/b}} account. This code will expire in 15 minutes.",
|
||||
|
|
@ -34,7 +43,7 @@
|
|||
"emails.recovery.subject": "Password Reset",
|
||||
"emails.recovery.hello": "Hello {{user}}",
|
||||
"emails.recovery.body": "Follow this link to reset your {{b}}{{project}}{{/b}} password.",
|
||||
"emails.recovery.footer": "If you didn’t ask to reset your password, you can ignore this message.",
|
||||
"emails.recovery.footer": "If you didn't ask to reset your password, you can ignore this message.",
|
||||
"emails.recovery.thanks": "Thanks",
|
||||
"emails.recovery.signature": "{{project}} team",
|
||||
"emails.invitation.subject": "Invitation to %s Team at %s",
|
||||
|
|
|
|||
|
|
@ -1,9 +1,5 @@
|
|||
<?php
|
||||
|
||||
const APP_PLATFORM_SERVER = 'server';
|
||||
const APP_PLATFORM_CLIENT = 'client';
|
||||
const APP_PLATFORM_CONSOLE = 'console';
|
||||
|
||||
return [
|
||||
APP_PLATFORM_CLIENT => [
|
||||
'key' => APP_PLATFORM_CLIENT,
|
||||
|
|
@ -15,7 +11,7 @@ return [
|
|||
[
|
||||
'key' => 'web',
|
||||
'name' => 'Web',
|
||||
'version' => '15.0.0',
|
||||
'version' => '16.0.2',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-web',
|
||||
'package' => 'https://www.npmjs.com/package/appwrite',
|
||||
'enabled' => true,
|
||||
|
|
@ -63,7 +59,7 @@ return [
|
|||
[
|
||||
'key' => 'flutter',
|
||||
'name' => 'Flutter',
|
||||
'version' => '12.0.4',
|
||||
'version' => '13.0.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-flutter',
|
||||
'package' => 'https://pub.dev/packages/appwrite',
|
||||
'enabled' => true,
|
||||
|
|
@ -81,7 +77,7 @@ return [
|
|||
[
|
||||
'key' => 'apple',
|
||||
'name' => 'Apple',
|
||||
'version' => '6.0.0',
|
||||
'version' => '7.0.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-apple',
|
||||
'package' => 'https://github.com/appwrite/sdk-for-apple',
|
||||
'enabled' => true,
|
||||
|
|
@ -116,7 +112,7 @@ return [
|
|||
[
|
||||
'key' => 'android',
|
||||
'name' => 'Android',
|
||||
'version' => '5.1.1',
|
||||
'version' => '6.0.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-android',
|
||||
'package' => 'https://search.maven.org/artifact/io.appwrite/sdk-for-android',
|
||||
'enabled' => true,
|
||||
|
|
@ -138,7 +134,7 @@ return [
|
|||
[
|
||||
'key' => 'react-native',
|
||||
'name' => 'React Native',
|
||||
'version' => '0.4.0',
|
||||
'version' => '0.5.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-react-native',
|
||||
'package' => 'https://npmjs.com/package/react-native-appwrite',
|
||||
'enabled' => true,
|
||||
|
|
@ -203,7 +199,7 @@ return [
|
|||
[
|
||||
'key' => 'web',
|
||||
'name' => 'Console',
|
||||
'version' => '0.6.3',
|
||||
'version' => '1.2.1',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-console',
|
||||
'package' => '',
|
||||
'enabled' => true,
|
||||
|
|
@ -214,14 +210,14 @@ return [
|
|||
'prism' => 'javascript',
|
||||
'source' => \realpath(__DIR__ . '/../sdks/console-web'),
|
||||
'gitUrl' => 'git@github.com:appwrite/sdk-for-console.git',
|
||||
'gitBranch' => 'main',
|
||||
'gitBranch' => 'dev',
|
||||
'gitRepoName' => 'sdk-for-console',
|
||||
'gitUserName' => 'appwrite',
|
||||
],
|
||||
[
|
||||
'key' => 'cli',
|
||||
'name' => 'Command Line',
|
||||
'version' => '5.0.5',
|
||||
'version' => '6.0.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-cli',
|
||||
'package' => 'https://www.npmjs.com/package/appwrite-cli',
|
||||
'enabled' => true,
|
||||
|
|
@ -249,7 +245,7 @@ return [
|
|||
[
|
||||
'key' => 'nodejs',
|
||||
'name' => 'Node.js',
|
||||
'version' => '13.0.0',
|
||||
'version' => '14.1.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-node',
|
||||
'package' => 'https://www.npmjs.com/package/node-appwrite',
|
||||
'enabled' => true,
|
||||
|
|
@ -267,7 +263,7 @@ return [
|
|||
[
|
||||
'key' => 'deno',
|
||||
'name' => 'Deno',
|
||||
'version' => '11.0.0',
|
||||
'version' => '12.1.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-deno',
|
||||
'package' => 'https://deno.land/x/appwrite',
|
||||
'enabled' => true,
|
||||
|
|
@ -285,7 +281,7 @@ return [
|
|||
[
|
||||
'key' => 'php',
|
||||
'name' => 'PHP',
|
||||
'version' => '11.0.2',
|
||||
'version' => '12.1.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-php',
|
||||
'package' => 'https://packagist.org/packages/appwrite/appwrite',
|
||||
'enabled' => true,
|
||||
|
|
@ -303,7 +299,7 @@ return [
|
|||
[
|
||||
'key' => 'python',
|
||||
'name' => 'Python',
|
||||
'version' => '5.0.3',
|
||||
'version' => '6.1.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-python',
|
||||
'package' => 'https://pypi.org/project/appwrite/',
|
||||
'enabled' => true,
|
||||
|
|
@ -321,7 +317,7 @@ return [
|
|||
[
|
||||
'key' => 'ruby',
|
||||
'name' => 'Ruby',
|
||||
'version' => '11.0.2',
|
||||
'version' => '12.1.1',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-ruby',
|
||||
'package' => 'https://rubygems.org/gems/appwrite',
|
||||
'enabled' => true,
|
||||
|
|
@ -339,10 +335,10 @@ return [
|
|||
[
|
||||
'key' => 'go',
|
||||
'name' => 'Go',
|
||||
'version' => '4.0.1',
|
||||
'version' => '0.2.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-go',
|
||||
'package' => '',
|
||||
'enabled' => false,
|
||||
'package' => 'https://github.com/appwrite/sdk-for-go',
|
||||
'enabled' => true,
|
||||
'beta' => true,
|
||||
'dev' => false,
|
||||
'hidden' => false,
|
||||
|
|
@ -354,28 +350,10 @@ return [
|
|||
'gitUserName' => 'appwrite',
|
||||
'gitBranch' => 'dev',
|
||||
],
|
||||
[
|
||||
'key' => 'java',
|
||||
'name' => 'Java',
|
||||
'version' => '4.0.2',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-java',
|
||||
'package' => '',
|
||||
'enabled' => false,
|
||||
'beta' => true,
|
||||
'dev' => false,
|
||||
'hidden' => false,
|
||||
'family' => APP_PLATFORM_SERVER,
|
||||
'prism' => 'java',
|
||||
'source' => \realpath(__DIR__ . '/../sdks/server-java'),
|
||||
'gitUrl' => 'git@github.com:appwrite/sdk-for-java.git',
|
||||
'gitRepoName' => 'sdk-for-java',
|
||||
'gitUserName' => 'appwrite',
|
||||
'gitBranch' => 'dev',
|
||||
],
|
||||
[
|
||||
'key' => 'dotnet',
|
||||
'name' => '.NET',
|
||||
'version' => '0.8.3',
|
||||
'version' => '0.10.1',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-dotnet',
|
||||
'package' => 'https://www.nuget.org/packages/Appwrite',
|
||||
'enabled' => true,
|
||||
|
|
@ -393,7 +371,7 @@ return [
|
|||
[
|
||||
'key' => 'dart',
|
||||
'name' => 'Dart',
|
||||
'version' => '11.0.3',
|
||||
'version' => '12.1.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-dart',
|
||||
'package' => 'https://pub.dev/packages/dart_appwrite',
|
||||
'enabled' => true,
|
||||
|
|
@ -411,7 +389,7 @@ return [
|
|||
[
|
||||
'key' => 'kotlin',
|
||||
'name' => 'Kotlin',
|
||||
'version' => '5.0.2',
|
||||
'version' => '6.1.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-kotlin',
|
||||
'package' => 'https://search.maven.org/artifact/io.appwrite/sdk-for-kotlin',
|
||||
'enabled' => true,
|
||||
|
|
@ -433,7 +411,7 @@ return [
|
|||
[
|
||||
'key' => 'swift',
|
||||
'name' => 'Swift',
|
||||
'version' => '5.0.2',
|
||||
'version' => '6.1.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-swift',
|
||||
'package' => 'https://github.com/appwrite/sdk-for-swift',
|
||||
'enabled' => true,
|
||||
|
|
|
|||
|
|
@ -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 [
|
||||
|
|
|
|||
|
|
@ -6,4 +6,4 @@
|
|||
|
||||
use Appwrite\Runtimes\Runtimes;
|
||||
|
||||
return (new Runtimes('v3'))->getAll();
|
||||
return (new Runtimes('v4'))->getAll();
|
||||
|
|
|
|||
51
app/config/runtimes/specifications.php
Normal file
51
app/config/runtimes/specifications.php
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
use Appwrite\Functions\Specification;
|
||||
|
||||
return [
|
||||
Specification::S_05VCPU_512MB => [
|
||||
'slug' => Specification::S_05VCPU_512MB,
|
||||
'memory' => 512,
|
||||
'cpus' => 0.5
|
||||
],
|
||||
Specification::S_1VCPU_512MB => [
|
||||
'slug' => Specification::S_1VCPU_512MB,
|
||||
'memory' => 512,
|
||||
'cpus' => 1
|
||||
],
|
||||
Specification::S_1VCPU_1GB => [
|
||||
'slug' => Specification::S_1VCPU_1GB,
|
||||
'memory' => 1024,
|
||||
'cpus' => 1
|
||||
],
|
||||
Specification::S_2VCPU_2GB => [
|
||||
'slug' => Specification::S_2VCPU_2GB,
|
||||
'memory' => 2048,
|
||||
'cpus' => 2
|
||||
],
|
||||
Specification::S_2VCPU_4GB => [
|
||||
'slug' => Specification::S_2VCPU_4GB,
|
||||
'memory' => 4096,
|
||||
'cpus' => 2
|
||||
],
|
||||
Specification::S_4VCPU_4GB => [
|
||||
'slug' => Specification::S_4VCPU_4GB,
|
||||
'memory' => 4096,
|
||||
'cpus' => 4
|
||||
],
|
||||
Specification::S_4VCPU_8GB => [
|
||||
'slug' => Specification::S_4VCPU_8GB,
|
||||
'memory' => 8192,
|
||||
'cpus' => 4
|
||||
],
|
||||
Specification::S_8VCPU_4GB => [
|
||||
'slug' => Specification::S_8VCPU_4GB,
|
||||
'memory' => 4096,
|
||||
'cpus' => 8
|
||||
],
|
||||
Specification::S_8VCPU_8GB => [
|
||||
'slug' => Specification::S_8VCPU_8GB,
|
||||
'memory' => 8192,
|
||||
'cpus' => 8
|
||||
]
|
||||
];
|
||||
9901
app/config/specs/open-api3-1.6.x-client.json
Normal file
9901
app/config/specs/open-api3-1.6.x-client.json
Normal file
File diff suppressed because it is too large
Load diff
38656
app/config/specs/open-api3-1.6.x-console.json
Normal file
38656
app/config/specs/open-api3-1.6.x-console.json
Normal file
File diff suppressed because it is too large
Load diff
27146
app/config/specs/open-api3-1.6.x-server.json
Normal file
27146
app/config/specs/open-api3-1.6.x-server.json
Normal file
File diff suppressed because it is too large
Load diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
10050
app/config/specs/swagger2-1.6.x-client.json
Normal file
10050
app/config/specs/swagger2-1.6.x-client.json
Normal file
File diff suppressed because it is too large
Load diff
39177
app/config/specs/swagger2-1.6.x-console.json
Normal file
39177
app/config/specs/swagger2-1.6.x-console.json
Normal file
File diff suppressed because it is too large
Load diff
27590
app/config/specs/swagger2-1.6.x-server.json
Normal file
27590
app/config/specs/swagger2-1.6.x-server.json
Normal file
File diff suppressed because it is too large
Load diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -6,6 +6,8 @@ return [
|
|||
'image/gif',
|
||||
'image/png',
|
||||
'image/webp',
|
||||
// 'image/heic',
|
||||
'image/avif',
|
||||
|
||||
// Video Files
|
||||
'video/mp4',
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
];
|
||||
|
|
|
|||
|
|
@ -144,8 +144,17 @@ return [
|
|||
],
|
||||
[
|
||||
'name' => '_APP_SYSTEM_EMAIL_ADDRESS',
|
||||
'description' => 'This is the sender email address that will appear on email messages sent to developers from the Appwrite console. The default value is \'team@appwrite.io\'. You should choose an email address that is allowed to be used from your SMTP server to avoid the server email ending in the users\' SPAM folders.',
|
||||
'description' => 'This is the sender email address that will appear on email messages sent to developers from the Appwrite console. The default value is \'noreply@appwrite.io\'. You should choose an email address that is allowed to be used from your SMTP server to avoid the server email ending in the users\' SPAM folders.',
|
||||
'introduction' => '0.7.0',
|
||||
'default' => 'noreply@appwrite.io',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
[
|
||||
'name' => '_APP_SYSTEM_TEAM_EMAIL',
|
||||
'description' => 'This is the sender email address that will appear in the generated specs. The default value is \'team@appwrite.io\'.',
|
||||
'introduction' => '1.6.0',
|
||||
'default' => 'team@appwrite.io',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
|
|
@ -184,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' => ''
|
||||
],
|
||||
[
|
||||
|
|
@ -198,7 +207,7 @@ return [
|
|||
],
|
||||
[
|
||||
'name' => '_APP_LOGGING_PROVIDER',
|
||||
'description' => 'This variable allows you to enable logging errors to 3rd party providers. This value is empty by default, set the value to one of \'sentry\', \'raygun\', \'appSignal\', \'logOwl\' to enable the logger.',
|
||||
'description' => 'Deprecated since 1.6.0, use `_APP_LOGGING_CONFIG` with DSN value instead. This variable allows you to enable logging errors to 3rd party providers. This value is empty by default, set the value to one of \'sentry\', \'raygun\', \'appSignal\', \'logOwl\' to enable the logger.',
|
||||
'introduction' => '0.12.0',
|
||||
'default' => '',
|
||||
'required' => false,
|
||||
|
|
@ -207,7 +216,7 @@ return [
|
|||
],
|
||||
[
|
||||
'name' => '_APP_LOGGING_CONFIG',
|
||||
'description' => 'This variable configures authentication to 3rd party error logging providers. If using Sentry, this should be \'SENTRY_API_KEY;SENTRY_APP_ID\'. If using Raygun, this should be Raygun API key. If using AppSignal, this should be AppSignal API key. If using LogOwl, this should be LogOwl Service Ticket.',
|
||||
'description' => 'This variable allows you to enable logging errors to third party providers. This value is empty by default, set a DSN value to one of the following `sentry://PROJECT_ID:SENTRY_API_KEY@SENTRY_HOST/`, , `logowl://SERVICE_TICKET@SERIVCE_HOST/` `raygun://RAYGUN_API_KEY/`, `appSignal://API_KEY/` to enable the logger.\n\nFor versions prior `1.5.6` you can use the old syntax.\n\nOld syntax: If using Sentry, this should be \'SENTRY_API_KEY;SENTRY_APP_ID\'. If using Raygun, this should be Raygun API key. If using AppSignal, this should be AppSignal API key. If using LogOwl, this should be LogOwl Service Ticket.',
|
||||
'introduction' => '0.12.0',
|
||||
'default' => '',
|
||||
'required' => false,
|
||||
|
|
@ -250,6 +259,15 @@ return [
|
|||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
[
|
||||
'name' => '_APP_CONSOLE_SESSION_ALERTS',
|
||||
'description' => 'This option allows you configure if a new login in the Appwrite Console should send an alert email to the user. It\'s disabled by default with value "disabled", and to enable it, pass value "enabled".',
|
||||
'introduction' => '1.6.0',
|
||||
'default' => 'disabled',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
|
|
@ -702,13 +720,22 @@ return [
|
|||
'variables' => [
|
||||
[
|
||||
'name' => '_APP_FUNCTIONS_SIZE_LIMIT',
|
||||
'description' => 'The maximum size deployment in bytes. The default value is 30MB.',
|
||||
'description' => 'The maximum size of a function in bytes. The default value is 30MB.',
|
||||
'introduction' => '0.13.0',
|
||||
'default' => '30000000',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
[
|
||||
'name' => '_APP_FUNCTIONS_BUILD_SIZE_LIMIT',
|
||||
'description' => 'The maximum size of a built deployment in bytes. The default value is 2,000,000,000 (2GB), and the maximum value is 4,294,967,295 (4.2GB).',
|
||||
'introduction' => '1.6.0',
|
||||
'default' => '2000000000',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
[
|
||||
'name' => '_APP_FUNCTIONS_TIMEOUT',
|
||||
'description' => 'The maximum number of seconds allowed as a timeout value when creating a new function. The default value is 900 seconds. This is the global limit, timeout for individual functions are configured in the function\'s settings or in appwrite.json.',
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
Subproject commit 0959b594b32f176819d4afb3a769afea212db789
|
||||
|
|
@ -55,10 +55,97 @@ use Utopia\Validator\Text;
|
|||
use Utopia\Validator\URL;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
$oauthDefaultSuccess = '/auth/oauth2/success';
|
||||
$oauthDefaultFailure = '/auth/oauth2/failure';
|
||||
$oauthDefaultSuccess = '/console/auth/oauth2/success';
|
||||
$oauthDefaultFailure = '/console/auth/oauth2/failure';
|
||||
|
||||
$createSession = function (string $userId, string $secret, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Locale $locale, Reader $geodb, Event $queueForEvents) {
|
||||
function sendSessionAlert(Locale $locale, Document $user, Document $project, Document $session, Mail $queueForMails)
|
||||
{
|
||||
$subject = $locale->getText("emails.sessionAlert.subject");
|
||||
$customTemplate = $project->getAttribute('templates', [])['email.sessionAlert-' . $locale->default] ?? [];
|
||||
|
||||
$message = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-session-alert.tpl');
|
||||
$message
|
||||
->setParam('{{hello}}', $locale->getText("emails.sessionAlert.hello"))
|
||||
->setParam('{{body}}', $locale->getText("emails.sessionAlert.body"))
|
||||
->setParam('{{listDevice}}', $locale->getText("emails.sessionAlert.listDevice"))
|
||||
->setParam('{{listIpAddress}}', $locale->getText("emails.sessionAlert.listIpAddress"))
|
||||
->setParam('{{listCountry}}', $locale->getText("emails.sessionAlert.listCountry"))
|
||||
->setParam('{{footer}}', $locale->getText("emails.sessionAlert.footer"))
|
||||
->setParam('{{thanks}}', $locale->getText("emails.sessionAlert.thanks"))
|
||||
->setParam('{{signature}}', $locale->getText("emails.sessionAlert.signature"));
|
||||
|
||||
$body = $message->render();
|
||||
|
||||
$smtp = $project->getAttribute('smtp', []);
|
||||
$smtpEnabled = $smtp['enabled'] ?? false;
|
||||
|
||||
$senderEmail = System::getEnv('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM);
|
||||
$senderName = System::getEnv('_APP_SYSTEM_EMAIL_NAME', APP_NAME . ' Server');
|
||||
$replyTo = "";
|
||||
|
||||
if ($smtpEnabled) {
|
||||
if (!empty($smtp['senderEmail'])) {
|
||||
$senderEmail = $smtp['senderEmail'];
|
||||
}
|
||||
if (!empty($smtp['senderName'])) {
|
||||
$senderName = $smtp['senderName'];
|
||||
}
|
||||
if (!empty($smtp['replyTo'])) {
|
||||
$replyTo = $smtp['replyTo'];
|
||||
}
|
||||
|
||||
$queueForMails
|
||||
->setSmtpHost($smtp['host'] ?? '')
|
||||
->setSmtpPort($smtp['port'] ?? '')
|
||||
->setSmtpUsername($smtp['username'] ?? '')
|
||||
->setSmtpPassword($smtp['password'] ?? '')
|
||||
->setSmtpSecure($smtp['secure'] ?? '');
|
||||
|
||||
if (!empty($customTemplate)) {
|
||||
if (!empty($customTemplate['senderEmail'])) {
|
||||
$senderEmail = $customTemplate['senderEmail'];
|
||||
}
|
||||
if (!empty($customTemplate['senderName'])) {
|
||||
$senderName = $customTemplate['senderName'];
|
||||
}
|
||||
if (!empty($customTemplate['replyTo'])) {
|
||||
$replyTo = $customTemplate['replyTo'];
|
||||
}
|
||||
|
||||
$body = $customTemplate['message'] ?? '';
|
||||
$subject = $customTemplate['subject'] ?? $subject;
|
||||
}
|
||||
|
||||
$queueForMails
|
||||
->setSmtpReplyTo($replyTo)
|
||||
->setSmtpSenderEmail($senderEmail)
|
||||
->setSmtpSenderName($senderName);
|
||||
}
|
||||
|
||||
$emailVariables = [
|
||||
'direction' => $locale->getText('settings.direction'),
|
||||
'date' => (new \DateTime())->format('F j'),
|
||||
'year' => (new \DateTime())->format('YYYY'),
|
||||
'time' => (new \DateTime())->format('H:i:s'),
|
||||
'user' => $user->getAttribute('name'),
|
||||
'project' => $project->getAttribute('name'),
|
||||
'device' => $session->getAttribute('clientName'),
|
||||
'ipAddress' => $session->getAttribute('ip'),
|
||||
'country' => $locale->getText('countries.' . $session->getAttribute('countryCode'), $locale->getText('locale.country.unknown')),
|
||||
];
|
||||
|
||||
$email = $user->getAttribute('email');
|
||||
|
||||
$queueForMails
|
||||
->setSubject($subject)
|
||||
->setBody($body)
|
||||
->setVariables($emailVariables)
|
||||
->setRecipient($email)
|
||||
->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) {
|
||||
$roles = Authorization::getRoles();
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
|
||||
$isAppUser = Auth::isAppUser($roles);
|
||||
|
|
@ -119,7 +206,6 @@ $createSession = function (string $userId, string $secret, Request $request, Res
|
|||
Permission::delete(Role::user($user->getId())),
|
||||
]));
|
||||
|
||||
$dbForProject->purgeCachedDocument('users', $user->getId());
|
||||
Authorization::skip(fn () => $dbForProject->deleteDocument('tokens', $verifiedToken->getId()));
|
||||
$dbForProject->purgeCachedDocument('users', $user->getId());
|
||||
|
||||
|
|
@ -138,6 +224,24 @@ $createSession = function (string $userId, string $secret, Request $request, Res
|
|||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed saving user to DB');
|
||||
}
|
||||
|
||||
$isAllowedTokenType = match ($verifiedToken->getAttribute('type')) {
|
||||
Auth::TOKEN_TYPE_MAGIC_URL,
|
||||
Auth::TOKEN_TYPE_EMAIL => false,
|
||||
default => true
|
||||
};
|
||||
|
||||
$hasUserEmail = $user->getAttribute('email', false) !== false;
|
||||
|
||||
$isSessionAlertsEnabled = $project->getAttribute('auths', [])['sessionAlerts'] ?? false;
|
||||
|
||||
$isNotFirstSession = $dbForProject->count('sessions', [
|
||||
Query::equal('userId', [$user->getId()]),
|
||||
]) !== 1;
|
||||
|
||||
if ($isAllowedTokenType && $hasUserEmail && $isSessionAlertsEnabled && $isNotFirstSession) {
|
||||
sendSessionAlert($locale, $user, $project, $session, $queueForMails);
|
||||
}
|
||||
|
||||
$queueForEvents
|
||||
->setParam('userId', $user->getId())
|
||||
->setParam('sessionId', $session->getId());
|
||||
|
|
@ -290,7 +394,7 @@ App::post('/v1/account')
|
|||
$existingTarget = $dbForProject->findOne('targets', [
|
||||
Query::equal('identifier', [$email]),
|
||||
]);
|
||||
if($existingTarget) {
|
||||
if ($existingTarget) {
|
||||
$user->setAttribute('targets', $existingTarget, Document::SET_TYPE_APPEND);
|
||||
}
|
||||
}
|
||||
|
|
@ -719,8 +823,9 @@ App::post('/v1/account/sessions/email')
|
|||
->inject('locale')
|
||||
->inject('geodb')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForMails')
|
||||
->inject('hooks')
|
||||
->action(function (string $email, string $password, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Locale $locale, Reader $geodb, Event $queueForEvents, Hooks $hooks) {
|
||||
->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();
|
||||
|
||||
|
|
@ -813,6 +918,14 @@ App::post('/v1/account/sessions/email')
|
|||
->setParam('sessionId', $session->getId())
|
||||
;
|
||||
|
||||
if ($project->getAttribute('auths', [])['sessionAlerts'] ?? false) {
|
||||
if ($dbForProject->count('sessions', [
|
||||
Query::equal('userId', [$user->getId()]),
|
||||
]) !== 1) {
|
||||
sendSessionAlert($locale, $user, $project, $session, $queueForMails);
|
||||
}
|
||||
}
|
||||
|
||||
$response->dynamic($session, Response::MODEL_SESSION);
|
||||
});
|
||||
|
||||
|
|
@ -981,6 +1094,7 @@ App::post('/v1/account/sessions/token')
|
|||
->inject('locale')
|
||||
->inject('geodb')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForMails')
|
||||
->action($createSession);
|
||||
|
||||
App::get('/v1/account/sessions/oauth2/:provider')
|
||||
|
|
@ -1765,7 +1879,7 @@ App::post('/v1/account/tokens/magic-url')
|
|||
$dbForProject->purgeCachedDocument('users', $user->getId());
|
||||
|
||||
if (empty($url)) {
|
||||
$url = $request->getProtocol() . '://' . $request->getHostname() . '/auth/magic-url';
|
||||
$url = $request->getProtocol() . '://' . $request->getHostname() . '/console/auth/magic-url';
|
||||
}
|
||||
|
||||
$url = Template::parseURL($url);
|
||||
|
|
@ -2142,6 +2256,7 @@ App::put('/v1/account/sessions/magic-url')
|
|||
->inject('locale')
|
||||
->inject('geodb')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForMails')
|
||||
->action($createSession);
|
||||
|
||||
App::put('/v1/account/sessions/phone')
|
||||
|
|
@ -2172,6 +2287,7 @@ App::put('/v1/account/sessions/phone')
|
|||
->inject('locale')
|
||||
->inject('geodb')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForMails')
|
||||
->action($createSession);
|
||||
|
||||
App::post('/v1/account/tokens/phone')
|
||||
|
|
@ -2274,7 +2390,18 @@ App::post('/v1/account/tokens/phone')
|
|||
$dbForProject->purgeCachedDocument('users', $user->getId());
|
||||
}
|
||||
|
||||
$secret = Auth::codeGenerator();
|
||||
$secret = null;
|
||||
$sendSMS = true;
|
||||
$mockNumbers = $project->getAttribute('auths', [])['mockNumbers'] ?? [];
|
||||
foreach ($mockNumbers as $mockNumber) {
|
||||
if ($mockNumber['phone'] === $phone) {
|
||||
$secret = $mockNumber['otp'];
|
||||
$sendSMS = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$secret ??= Auth::codeGenerator();
|
||||
$expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), Auth::TOKEN_EXPIRATION_OTP));
|
||||
|
||||
$token = new Document([
|
||||
|
|
@ -2299,35 +2426,37 @@ App::post('/v1/account/tokens/phone')
|
|||
|
||||
$dbForProject->purgeCachedDocument('users', $user->getId());
|
||||
|
||||
$message = Template::fromFile(__DIR__ . '/../../config/locale/templates/sms-base.tpl');
|
||||
if ($sendSMS) {
|
||||
$message = Template::fromFile(__DIR__ . '/../../config/locale/templates/sms-base.tpl');
|
||||
|
||||
$customTemplate = $project->getAttribute('templates', [])['sms.login-' . $locale->default] ?? [];
|
||||
if (!empty($customTemplate)) {
|
||||
$message = $customTemplate['message'] ?? $message;
|
||||
$customTemplate = $project->getAttribute('templates', [])['sms.login-' . $locale->default] ?? [];
|
||||
if (!empty($customTemplate)) {
|
||||
$message = $customTemplate['message'] ?? $message;
|
||||
}
|
||||
|
||||
$messageContent = Template::fromString($locale->getText("sms.verification.body"));
|
||||
$messageContent
|
||||
->setParam('{{project}}', $project->getAttribute('name'))
|
||||
->setParam('{{secret}}', $secret);
|
||||
$messageContent = \strip_tags($messageContent->render());
|
||||
$message = $message->setParam('{{token}}', $messageContent);
|
||||
|
||||
$message = $message->render();
|
||||
|
||||
$messageDoc = new Document([
|
||||
'$id' => $token->getId(),
|
||||
'data' => [
|
||||
'content' => $message,
|
||||
],
|
||||
]);
|
||||
|
||||
$queueForMessaging
|
||||
->setType(MESSAGE_SEND_TYPE_INTERNAL)
|
||||
->setMessage($messageDoc)
|
||||
->setRecipients([$phone])
|
||||
->setProviderType(MESSAGE_TYPE_SMS);
|
||||
}
|
||||
|
||||
$messageContent = Template::fromString($locale->getText("sms.verification.body"));
|
||||
$messageContent
|
||||
->setParam('{{project}}', $project->getAttribute('name'))
|
||||
->setParam('{{secret}}', $secret);
|
||||
$messageContent = \strip_tags($messageContent->render());
|
||||
$message = $message->setParam('{{token}}', $messageContent);
|
||||
|
||||
$message = $message->render();
|
||||
|
||||
$messageDoc = new Document([
|
||||
'$id' => $token->getId(),
|
||||
'data' => [
|
||||
'content' => $message,
|
||||
],
|
||||
]);
|
||||
|
||||
$queueForMessaging
|
||||
->setType(MESSAGE_SEND_TYPE_INTERNAL)
|
||||
->setMessage($messageDoc)
|
||||
->setRecipients([$phone])
|
||||
->setProviderType(MESSAGE_TYPE_SMS);
|
||||
|
||||
// Set to unhashed secret for events and server responses
|
||||
$token->setAttribute('secret', $secret);
|
||||
|
||||
|
|
@ -2342,7 +2471,8 @@ App::post('/v1/account/tokens/phone')
|
|||
->dynamic($token, Response::MODEL_TOKEN);
|
||||
});
|
||||
|
||||
App::post('/v1/account/jwt')
|
||||
App::post('/v1/account/jwts')
|
||||
->alias('/v1/account/jwt')
|
||||
->desc('Create JWT')
|
||||
->groups(['api', 'account', 'auth'])
|
||||
->label('scope', 'account')
|
||||
|
|
@ -2375,15 +2505,11 @@ App::post('/v1/account/jwt')
|
|||
throw new Exception(Exception::USER_SESSION_NOT_FOUND);
|
||||
}
|
||||
|
||||
$jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 900, 10); // Instantiate with key, algo, maxAge and leeway.
|
||||
$jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 900, 0);
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
->dynamic(new Document(['jwt' => $jwt->encode([
|
||||
// 'uid' => 1,
|
||||
// 'aud' => 'http://site.com',
|
||||
// 'scopes' => ['user'],
|
||||
// 'iss' => 'http://api.mysite.com',
|
||||
'userId' => $user->getId(),
|
||||
'sessionId' => $current->getId(),
|
||||
])]), Response::MODEL_JWT);
|
||||
|
|
@ -3347,7 +3473,8 @@ App::post('/v1/account/verification/phone')
|
|||
throw new Exception(Exception::GENERAL_PHONE_DISABLED, 'Phone provider not configured');
|
||||
}
|
||||
|
||||
if (empty($user->getAttribute('phone'))) {
|
||||
$phone = $user->getAttribute('phone');
|
||||
if (empty($phone)) {
|
||||
throw new Exception(Exception::USER_PHONE_NOT_FOUND);
|
||||
}
|
||||
|
||||
|
|
@ -3358,7 +3485,19 @@ App::post('/v1/account/verification/phone')
|
|||
$roles = Authorization::getRoles();
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
|
||||
$isAppUser = Auth::isAppUser($roles);
|
||||
$secret = Auth::codeGenerator();
|
||||
|
||||
$secret = null;
|
||||
$sendSMS = true;
|
||||
$mockNumbers = $project->getAttribute('auths', [])['mockNumbers'] ?? [];
|
||||
foreach ($mockNumbers as $mockNumber) {
|
||||
if ($mockNumber['phone'] === $phone) {
|
||||
$secret = $mockNumber['otp'];
|
||||
$sendSMS = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$secret ??= Auth::codeGenerator();
|
||||
$expire = DateTime::addSeconds(new \DateTime(), Auth::TOKEN_EXPIRATION_CONFIRM);
|
||||
|
||||
$verification = new Document([
|
||||
|
|
@ -3383,35 +3522,37 @@ App::post('/v1/account/verification/phone')
|
|||
|
||||
$dbForProject->purgeCachedDocument('users', $user->getId());
|
||||
|
||||
$message = Template::fromFile(__DIR__ . '/../../config/locale/templates/sms-base.tpl');
|
||||
if ($sendSMS) {
|
||||
$message = Template::fromFile(__DIR__ . '/../../config/locale/templates/sms-base.tpl');
|
||||
|
||||
$customTemplate = $project->getAttribute('templates', [])['sms.verification-' . $locale->default] ?? [];
|
||||
if (!empty($customTemplate)) {
|
||||
$message = $customTemplate['message'] ?? $message;
|
||||
$customTemplate = $project->getAttribute('templates', [])['sms.verification-' . $locale->default] ?? [];
|
||||
if (!empty($customTemplate)) {
|
||||
$message = $customTemplate['message'] ?? $message;
|
||||
}
|
||||
|
||||
$messageContent = Template::fromString($locale->getText("sms.verification.body"));
|
||||
$messageContent
|
||||
->setParam('{{project}}', $project->getAttribute('name'))
|
||||
->setParam('{{secret}}', $secret);
|
||||
$messageContent = \strip_tags($messageContent->render());
|
||||
$message = $message->setParam('{{token}}', $messageContent);
|
||||
|
||||
$message = $message->render();
|
||||
|
||||
$messageDoc = new Document([
|
||||
'$id' => $verification->getId(),
|
||||
'data' => [
|
||||
'content' => $message,
|
||||
],
|
||||
]);
|
||||
|
||||
$queueForMessaging
|
||||
->setType(MESSAGE_SEND_TYPE_INTERNAL)
|
||||
->setMessage($messageDoc)
|
||||
->setRecipients([$user->getAttribute('phone')])
|
||||
->setProviderType(MESSAGE_TYPE_SMS);
|
||||
}
|
||||
|
||||
$messageContent = Template::fromString($locale->getText("sms.verification.body"));
|
||||
$messageContent
|
||||
->setParam('{{project}}', $project->getAttribute('name'))
|
||||
->setParam('{{secret}}', $secret);
|
||||
$messageContent = \strip_tags($messageContent->render());
|
||||
$message = $message->setParam('{{token}}', $messageContent);
|
||||
|
||||
$message = $message->render();
|
||||
|
||||
$messageDoc = new Document([
|
||||
'$id' => $verification->getId(),
|
||||
'data' => [
|
||||
'content' => $message,
|
||||
],
|
||||
]);
|
||||
|
||||
$queueForMessaging
|
||||
->setType(MESSAGE_SEND_TYPE_INTERNAL)
|
||||
->setMessage($messageDoc)
|
||||
->setRecipients([$user->getAttribute('phone')])
|
||||
->setProviderType(MESSAGE_TYPE_SMS);
|
||||
|
||||
// Set to unhashed secret for events and server responses
|
||||
$verification->setAttribute('secret', $secret);
|
||||
|
||||
|
|
@ -3542,7 +3683,7 @@ App::patch('/v1/account/mfa')
|
|||
});
|
||||
|
||||
App::get('/v1/account/mfa/factors')
|
||||
->desc('List Factors')
|
||||
->desc('List factors')
|
||||
->groups(['api', 'account', 'mfa'])
|
||||
->label('scope', 'account')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
|
||||
|
|
@ -3574,7 +3715,7 @@ App::get('/v1/account/mfa/factors')
|
|||
});
|
||||
|
||||
App::post('/v1/account/mfa/authenticators/:type')
|
||||
->desc('Create Authenticator')
|
||||
->desc('Create authenticator')
|
||||
->groups(['api', 'account'])
|
||||
->label('event', 'users.[userId].update.mfa')
|
||||
->label('scope', 'account')
|
||||
|
|
@ -3646,7 +3787,7 @@ App::post('/v1/account/mfa/authenticators/:type')
|
|||
});
|
||||
|
||||
App::put('/v1/account/mfa/authenticators/:type')
|
||||
->desc('Verify Authenticator')
|
||||
->desc('Verify authenticator')
|
||||
->groups(['api', 'account'])
|
||||
->label('event', 'users.[userId].update.mfa')
|
||||
->label('scope', 'account')
|
||||
|
|
@ -3711,7 +3852,7 @@ App::put('/v1/account/mfa/authenticators/:type')
|
|||
});
|
||||
|
||||
App::post('/v1/account/mfa/recovery-codes')
|
||||
->desc('Create MFA Recovery Codes')
|
||||
->desc('Create MFA recovery codes')
|
||||
->groups(['api', 'account'])
|
||||
->label('event', 'users.[userId].update.mfa')
|
||||
->label('scope', 'account')
|
||||
|
|
@ -3753,7 +3894,7 @@ App::post('/v1/account/mfa/recovery-codes')
|
|||
});
|
||||
|
||||
App::patch('/v1/account/mfa/recovery-codes')
|
||||
->desc('Regenerate MFA Recovery Codes')
|
||||
->desc('Regenerate MFA recovery codes')
|
||||
->groups(['api', 'account', 'mfaProtected'])
|
||||
->label('event', 'users.[userId].update.mfa')
|
||||
->label('scope', 'account')
|
||||
|
|
@ -3794,7 +3935,7 @@ App::patch('/v1/account/mfa/recovery-codes')
|
|||
});
|
||||
|
||||
App::get('/v1/account/mfa/recovery-codes')
|
||||
->desc('Get 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])
|
||||
|
|
@ -3824,8 +3965,8 @@ App::get('/v1/account/mfa/recovery-codes')
|
|||
});
|
||||
|
||||
App::delete('/v1/account/mfa/authenticators/:type')
|
||||
->desc('Delete Authenticator')
|
||||
->groups(['api', 'account'])
|
||||
->desc('Delete authenticator')
|
||||
->groups(['api', 'account', 'mfaProtected'])
|
||||
->label('event', 'users.[userId].delete.mfa')
|
||||
->label('scope', 'account')
|
||||
->label('audits.event', 'user.update')
|
||||
|
|
@ -3838,12 +3979,11 @@ App::delete('/v1/account/mfa/authenticators/:type')
|
|||
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
|
||||
->label('sdk.response.model', Response::MODEL_NONE)
|
||||
->param('type', null, new WhiteList([Type::TOTP]), 'Type of authenticator.')
|
||||
->param('otp', '', new Text(256), 'Valid verification token.')
|
||||
->inject('response')
|
||||
->inject('user')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->action(function (string $type, string $otp, Response $response, Document $user, Database $dbForProject, Event $queueForEvents) {
|
||||
->action(function (string $type, Response $response, Document $user, Database $dbForProject, Event $queueForEvents) {
|
||||
|
||||
$authenticator = (match ($type) {
|
||||
Type::TOTP => TOTP::getAuthenticatorFromUser($user),
|
||||
|
|
@ -3854,27 +3994,6 @@ App::delete('/v1/account/mfa/authenticators/:type')
|
|||
throw new Exception(Exception::USER_AUTHENTICATOR_NOT_FOUND);
|
||||
}
|
||||
|
||||
$success = (match ($type) {
|
||||
Type::TOTP => Challenge\TOTP::verify($user, $otp),
|
||||
default => false
|
||||
});
|
||||
|
||||
if (!$success) {
|
||||
$mfaRecoveryCodes = $user->getAttribute('mfaRecoveryCodes', []);
|
||||
if (in_array($otp, $mfaRecoveryCodes)) {
|
||||
$mfaRecoveryCodes = array_diff($mfaRecoveryCodes, [$otp]);
|
||||
$mfaRecoveryCodes = array_values($mfaRecoveryCodes);
|
||||
$user->setAttribute('mfaRecoveryCodes', $mfaRecoveryCodes);
|
||||
$dbForProject->updateDocument('users', $user->getId(), $user);
|
||||
|
||||
$success = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$success) {
|
||||
throw new Exception(Exception::USER_INVALID_TOKEN);
|
||||
}
|
||||
|
||||
$dbForProject->deleteDocument('authenticators', $authenticator->getId());
|
||||
$dbForProject->purgeCachedDocument('users', $user->getId());
|
||||
|
||||
|
|
@ -3884,7 +4003,7 @@ App::delete('/v1/account/mfa/authenticators/:type')
|
|||
});
|
||||
|
||||
App::post('/v1/account/mfa/challenge')
|
||||
->desc('Create MFA Challenge')
|
||||
->desc('Create MFA challenge')
|
||||
->groups(['api', 'account', 'mfa'])
|
||||
->label('scope', 'account')
|
||||
->label('event', 'users.[userId].challenges.[challengeId].create')
|
||||
|
|
@ -3899,7 +4018,7 @@ App::post('/v1/account/mfa/challenge')
|
|||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_MFA_CHALLENGE)
|
||||
->label('abuse-limit', 10)
|
||||
->label('abuse-key', 'url:{url},token:{param-token}')
|
||||
->label('abuse-key', 'url:{url},userId:{userId}')
|
||||
->param('factor', '', new WhiteList([Type::EMAIL, Type::PHONE, Type::TOTP, Type::RECOVERY_CODE]), 'Factor used for verification. Must be one of following: `' . Type::EMAIL . '`, `' . Type::PHONE . '`, `' . Type::TOTP . '`, `' . Type::RECOVERY_CODE . '`.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
|
|
@ -4072,7 +4191,7 @@ App::post('/v1/account/mfa/challenge')
|
|||
});
|
||||
|
||||
App::put('/v1/account/mfa/challenge')
|
||||
->desc('Create MFA Challenge (confirmation)')
|
||||
->desc('Create MFA challenge (confirmation)')
|
||||
->groups(['api', 'account', 'mfa'])
|
||||
->label('scope', 'account')
|
||||
->label('event', 'users.[userId].sessions.[sessionId].create')
|
||||
|
|
@ -4086,7 +4205,7 @@ App::put('/v1/account/mfa/challenge')
|
|||
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
|
||||
->label('sdk.response.model', Response::MODEL_SESSION)
|
||||
->label('abuse-limit', 10)
|
||||
->label('abuse-key', 'userId:{param-userId}')
|
||||
->label('abuse-key', 'url:{url},challengeId:{param-challengeId}')
|
||||
->param('challengeId', '', new Text(256), 'ID of the challenge.')
|
||||
->param('otp', '', new Text(256), 'Valid verification token.')
|
||||
->inject('project')
|
||||
|
|
@ -4333,7 +4452,7 @@ App::delete('/v1/account/targets/:targetId/push')
|
|||
$response->noContent();
|
||||
});
|
||||
App::get('/v1/account/identities')
|
||||
->desc('List Identities')
|
||||
->desc('List identities')
|
||||
->groups(['api', 'account'])
|
||||
->label('scope', 'account')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
|
||||
|
|
|
|||
|
|
@ -550,7 +550,7 @@ App::get('/v1/avatars/initials')
|
|||
});
|
||||
|
||||
App::get('/v1/cards/cloud')
|
||||
->desc('Get Front Of Cloud Card')
|
||||
->desc('Get front Of Cloud Card')
|
||||
->groups(['api', 'avatars'])
|
||||
->label('scope', 'avatars.read')
|
||||
->label('cache', true)
|
||||
|
|
@ -757,7 +757,7 @@ App::get('/v1/cards/cloud')
|
|||
});
|
||||
|
||||
App::get('/v1/cards/cloud-back')
|
||||
->desc('Get Back Of Cloud Card')
|
||||
->desc('Get back Of Cloud Card')
|
||||
->groups(['api', 'avatars'])
|
||||
->label('scope', 'avatars.read')
|
||||
->label('cache', true)
|
||||
|
|
@ -835,7 +835,7 @@ App::get('/v1/cards/cloud-back')
|
|||
});
|
||||
|
||||
App::get('/v1/cards/cloud-og')
|
||||
->desc('Get OG Image From Cloud Card')
|
||||
->desc('Get OG image From Cloud Card')
|
||||
->groups(['api', 'avatars'])
|
||||
->label('scope', 'avatars.read')
|
||||
->label('cache', true)
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ App::get('/v1/console/variables')
|
|||
});
|
||||
|
||||
App::post('/v1/console/assistant')
|
||||
->desc('Ask Query')
|
||||
->desc('Ask query')
|
||||
->groups(['api', 'assistant'])
|
||||
->label('scope', 'assistant.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -21,12 +22,15 @@ use Utopia\Audit\Audit;
|
|||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception as DatabaseException;
|
||||
use Utopia\Database\Exception\Authorization as AuthorizationException;
|
||||
use Utopia\Database\Exception\Conflict as ConflictException;
|
||||
use Utopia\Database\Exception\Duplicate as DuplicateException;
|
||||
use Utopia\Database\Exception\Limit as LimitException;
|
||||
use Utopia\Database\Exception\Query as QueryException;
|
||||
use Utopia\Database\Exception\Restricted as RestrictedException;
|
||||
use Utopia\Database\Exception\Structure as StructureException;
|
||||
use Utopia\Database\Exception\Truncate as TruncateException;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Helpers\Permission;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
|
|
@ -229,13 +233,15 @@ function updateAttribute(
|
|||
Database $dbForProject,
|
||||
Event $queueForEvents,
|
||||
string $type,
|
||||
int $size = null,
|
||||
string $filter = null,
|
||||
string|bool|int|float $default = null,
|
||||
bool $required = null,
|
||||
int|float $min = null,
|
||||
int|float $max = null,
|
||||
array $elements = null,
|
||||
array $options = []
|
||||
array $options = [],
|
||||
string $newKey = null,
|
||||
): Document {
|
||||
$db = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
|
||||
|
|
@ -281,6 +287,10 @@ function updateAttribute(
|
|||
->setAttribute('default', $default)
|
||||
->setAttribute('required', $required);
|
||||
|
||||
if (!empty($size)) {
|
||||
$attribute->setAttribute('size', $size);
|
||||
}
|
||||
|
||||
$formatOptions = $attribute->getAttribute('formatOptions');
|
||||
|
||||
switch ($attribute->getAttribute('format')) {
|
||||
|
|
@ -346,6 +356,7 @@ function updateAttribute(
|
|||
$dbForProject->updateRelationship(
|
||||
collection: $collectionId,
|
||||
id: $key,
|
||||
newKey: $newKey,
|
||||
onDelete: $primaryDocumentOptions['onDelete'],
|
||||
);
|
||||
|
||||
|
|
@ -353,22 +364,52 @@ function updateAttribute(
|
|||
$relatedCollection = $dbForProject->getDocument('database_' . $db->getInternalId(), $primaryDocumentOptions['relatedCollection']);
|
||||
|
||||
$relatedAttribute = $dbForProject->getDocument('attributes', $db->getInternalId() . '_' . $relatedCollection->getInternalId() . '_' . $primaryDocumentOptions['twoWayKey']);
|
||||
|
||||
if (!empty($newKey) && $newKey !== $key) {
|
||||
$options['twoWayKey'] = $newKey;
|
||||
}
|
||||
|
||||
$relatedOptions = \array_merge($relatedAttribute->getAttribute('options'), $options);
|
||||
$relatedAttribute->setAttribute('options', $relatedOptions);
|
||||
$dbForProject->updateDocument('attributes', $db->getInternalId() . '_' . $relatedCollection->getInternalId() . '_' . $primaryDocumentOptions['twoWayKey'], $relatedAttribute);
|
||||
|
||||
$dbForProject->purgeCachedDocument('database_' . $db->getInternalId(), $relatedCollection->getId());
|
||||
}
|
||||
} else {
|
||||
$dbForProject->updateAttribute(
|
||||
collection: $collectionId,
|
||||
id: $key,
|
||||
required: $required,
|
||||
default: $default,
|
||||
formatOptions: $options ?? null
|
||||
);
|
||||
try {
|
||||
$dbForProject->updateAttribute(
|
||||
collection: $collectionId,
|
||||
id: $key,
|
||||
size: $size,
|
||||
required: $required,
|
||||
default: $default,
|
||||
formatOptions: $options ?? null,
|
||||
newKey: $newKey ?? null
|
||||
);
|
||||
} catch (TruncateException) {
|
||||
throw new Exception(Exception::ATTRIBUTE_INVALID_RESIZE);
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($newKey) && $key !== $newKey) {
|
||||
// Delete attribute and recreate since we can't modify IDs
|
||||
$original = clone $attribute;
|
||||
|
||||
$dbForProject->deleteDocument('attributes', $attribute->getId());
|
||||
|
||||
$attribute
|
||||
->setAttribute('$id', ID::custom($db->getInternalId() . '_' . $collection->getInternalId() . '_' . $newKey))
|
||||
->setAttribute('key', $newKey);
|
||||
|
||||
try {
|
||||
$attribute = $dbForProject->createDocument('attributes', $attribute);
|
||||
} catch (DatabaseException|PDOException) {
|
||||
$attribute = $dbForProject->createDocument('attributes', $original);
|
||||
}
|
||||
} else {
|
||||
$attribute = $dbForProject->updateDocument('attributes', $db->getInternalId() . '_' . $collection->getInternalId() . '_' . $key, $attribute);
|
||||
}
|
||||
|
||||
$attribute = $dbForProject->updateDocument('attributes', $db->getInternalId() . '_' . $collection->getInternalId() . '_' . $key, $attribute);
|
||||
$dbForProject->purgeCachedDocument('database_' . $db->getInternalId(), $collection->getId());
|
||||
|
||||
$queueForEvents
|
||||
|
|
@ -413,7 +454,8 @@ App::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;
|
||||
|
||||
|
|
@ -463,6 +505,7 @@ App::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)
|
||||
|
|
@ -694,7 +737,8 @@ App::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);
|
||||
|
||||
|
|
@ -717,6 +761,9 @@ App::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();
|
||||
});
|
||||
|
||||
|
|
@ -1153,6 +1200,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/string
|
|||
'filters' => $filters,
|
||||
]), $response, $dbForProject, $queueForDatabase, $queueForEvents);
|
||||
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_ACCEPTED)
|
||||
->dynamic($attribute, Response::MODEL_ATTRIBUTE_STRING);
|
||||
|
|
@ -1860,10 +1908,12 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/strin
|
|||
->param('key', '', new Key(), 'Attribute Key.')
|
||||
->param('required', null, new Boolean(), 'Is attribute required?')
|
||||
->param('default', null, new Nullable(new Text(0, 0)), 'Default value for attribute when not provided. Cannot be set when attribute is required.')
|
||||
->param('size', null, new Integer(), 'Maximum size of the string attribute.', true)
|
||||
->param('newKey', null, new Key(), 'New attribute key.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, Response $response, Database $dbForProject, Event $queueForEvents) {
|
||||
->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, ?int $size, ?string $newKey, Response $response, Database $dbForProject, Event $queueForEvents) {
|
||||
|
||||
$attribute = updateAttribute(
|
||||
databaseId: $databaseId,
|
||||
|
|
@ -1872,8 +1922,10 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/strin
|
|||
dbForProject: $dbForProject,
|
||||
queueForEvents: $queueForEvents,
|
||||
type: Database::VAR_STRING,
|
||||
size: $size,
|
||||
default: $default,
|
||||
required: $required
|
||||
required: $required,
|
||||
newKey: $newKey
|
||||
);
|
||||
|
||||
$response
|
||||
|
|
@ -1899,10 +1951,11 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/email
|
|||
->param('key', '', new Key(), 'Attribute Key.')
|
||||
->param('required', null, new Boolean(), 'Is attribute required?')
|
||||
->param('default', null, new Nullable(new Email()), 'Default value for attribute when not provided. Cannot be set when attribute is required.')
|
||||
->param('newKey', null, new Key(), 'New attribute key.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, Response $response, Database $dbForProject, Event $queueForEvents) {
|
||||
->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, ?string $newKey, Response $response, Database $dbForProject, Event $queueForEvents) {
|
||||
$attribute = updateAttribute(
|
||||
databaseId: $databaseId,
|
||||
collectionId: $collectionId,
|
||||
|
|
@ -1912,7 +1965,8 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/email
|
|||
type: Database::VAR_STRING,
|
||||
filter: APP_DATABASE_ATTRIBUTE_EMAIL,
|
||||
default: $default,
|
||||
required: $required
|
||||
required: $required,
|
||||
newKey: $newKey
|
||||
);
|
||||
|
||||
$response
|
||||
|
|
@ -1939,10 +1993,11 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/enum/
|
|||
->param('elements', null, new ArrayList(new Text(DATABASE::LENGTH_KEY), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of elements in enumerated type. Uses length of longest element to determine size. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' elements are allowed, each ' . DATABASE::LENGTH_KEY . ' characters long.')
|
||||
->param('required', null, new Boolean(), 'Is attribute required?')
|
||||
->param('default', null, new Nullable(new Text(0)), 'Default value for attribute when not provided. Cannot be set when attribute is required.')
|
||||
->param('newKey', null, new Key(), 'New attribute key.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->action(function (string $databaseId, string $collectionId, string $key, ?array $elements, ?bool $required, ?string $default, Response $response, Database $dbForProject, Event $queueForEvents) {
|
||||
->action(function (string $databaseId, string $collectionId, string $key, ?array $elements, ?bool $required, ?string $default, ?string $newKey, Response $response, Database $dbForProject, Event $queueForEvents) {
|
||||
$attribute = updateAttribute(
|
||||
databaseId: $databaseId,
|
||||
collectionId: $collectionId,
|
||||
|
|
@ -1953,7 +2008,8 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/enum/
|
|||
filter: APP_DATABASE_ATTRIBUTE_ENUM,
|
||||
default: $default,
|
||||
required: $required,
|
||||
elements: $elements
|
||||
elements: $elements,
|
||||
newKey: $newKey
|
||||
);
|
||||
|
||||
$response
|
||||
|
|
@ -1979,10 +2035,11 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/ip/:k
|
|||
->param('key', '', new Key(), 'Attribute Key.')
|
||||
->param('required', null, new Boolean(), 'Is attribute required?')
|
||||
->param('default', null, new Nullable(new IP()), 'Default value for attribute when not provided. Cannot be set when attribute is required.')
|
||||
->param('newKey', null, new Key(), 'New attribute key.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, Response $response, Database $dbForProject, Event $queueForEvents) {
|
||||
->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, ?string $newKey, Response $response, Database $dbForProject, Event $queueForEvents) {
|
||||
$attribute = updateAttribute(
|
||||
databaseId: $databaseId,
|
||||
collectionId: $collectionId,
|
||||
|
|
@ -1992,7 +2049,8 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/ip/:k
|
|||
type: Database::VAR_STRING,
|
||||
filter: APP_DATABASE_ATTRIBUTE_IP,
|
||||
default: $default,
|
||||
required: $required
|
||||
required: $required,
|
||||
newKey: $newKey
|
||||
);
|
||||
|
||||
$response
|
||||
|
|
@ -2018,10 +2076,11 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/url/:
|
|||
->param('key', '', new Key(), 'Attribute Key.')
|
||||
->param('required', null, new Boolean(), 'Is attribute required?')
|
||||
->param('default', null, new Nullable(new URL()), 'Default value for attribute when not provided. Cannot be set when attribute is required.')
|
||||
->param('newKey', null, new Key(), 'New attribute key.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, Response $response, Database $dbForProject, Event $queueForEvents) {
|
||||
->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, ?string $newKey, Response $response, Database $dbForProject, Event $queueForEvents) {
|
||||
$attribute = updateAttribute(
|
||||
databaseId: $databaseId,
|
||||
collectionId: $collectionId,
|
||||
|
|
@ -2031,7 +2090,8 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/url/:
|
|||
type: Database::VAR_STRING,
|
||||
filter: APP_DATABASE_ATTRIBUTE_URL,
|
||||
default: $default,
|
||||
required: $required
|
||||
required: $required,
|
||||
newKey: $newKey
|
||||
);
|
||||
|
||||
$response
|
||||
|
|
@ -2059,10 +2119,11 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/integ
|
|||
->param('min', null, new Integer(), 'Minimum value to enforce on new documents')
|
||||
->param('max', null, new Integer(), 'Maximum value to enforce on new documents')
|
||||
->param('default', null, new Nullable(new Integer()), 'Default value for attribute when not provided. Cannot be set when attribute is required.')
|
||||
->param('newKey', null, new Key(), 'New attribute key.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?int $min, ?int $max, ?int $default, Response $response, Database $dbForProject, Event $queueForEvents) {
|
||||
->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(
|
||||
databaseId: $databaseId,
|
||||
collectionId: $collectionId,
|
||||
|
|
@ -2073,7 +2134,8 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/integ
|
|||
default: $default,
|
||||
required: $required,
|
||||
min: $min,
|
||||
max: $max
|
||||
max: $max,
|
||||
newKey: $newKey
|
||||
);
|
||||
|
||||
$formatOptions = $attribute->getAttribute('formatOptions', []);
|
||||
|
|
@ -2108,10 +2170,11 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/float
|
|||
->param('min', null, new FloatValidator(), 'Minimum value to enforce on new documents')
|
||||
->param('max', null, new FloatValidator(), 'Maximum value to enforce on new documents')
|
||||
->param('default', null, new Nullable(new FloatValidator()), 'Default value for attribute when not provided. Cannot be set when attribute is required.')
|
||||
->param('newKey', null, new Key(), 'New attribute key.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?float $min, ?float $max, ?float $default, Response $response, Database $dbForProject, Event $queueForEvents) {
|
||||
->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(
|
||||
databaseId: $databaseId,
|
||||
collectionId: $collectionId,
|
||||
|
|
@ -2122,7 +2185,8 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/float
|
|||
default: $default,
|
||||
required: $required,
|
||||
min: $min,
|
||||
max: $max
|
||||
max: $max,
|
||||
newKey: $newKey
|
||||
);
|
||||
|
||||
$formatOptions = $attribute->getAttribute('formatOptions', []);
|
||||
|
|
@ -2155,10 +2219,11 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/boole
|
|||
->param('key', '', new Key(), 'Attribute Key.')
|
||||
->param('required', null, new Boolean(), 'Is attribute required?')
|
||||
->param('default', null, new Nullable(new Boolean()), 'Default value for attribute when not provided. Cannot be set when attribute is required.')
|
||||
->param('newKey', null, new Key(), 'New attribute key.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?bool $default, Response $response, Database $dbForProject, Event $queueForEvents) {
|
||||
->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?bool $default, ?string $newKey, Response $response, Database $dbForProject, Event $queueForEvents) {
|
||||
$attribute = updateAttribute(
|
||||
databaseId: $databaseId,
|
||||
collectionId: $collectionId,
|
||||
|
|
@ -2167,7 +2232,8 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/boole
|
|||
queueForEvents: $queueForEvents,
|
||||
type: Database::VAR_BOOLEAN,
|
||||
default: $default,
|
||||
required: $required
|
||||
required: $required,
|
||||
newKey: $newKey
|
||||
);
|
||||
|
||||
$response
|
||||
|
|
@ -2193,10 +2259,11 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/datet
|
|||
->param('key', '', new Key(), 'Attribute Key.')
|
||||
->param('required', null, new Boolean(), 'Is attribute required?')
|
||||
->param('default', null, new Nullable(new DatetimeValidator()), 'Default value for attribute when not provided. Cannot be set when attribute is required.')
|
||||
->param('newKey', null, new Key(), 'New attribute key.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, Response $response, Database $dbForProject, Event $queueForEvents) {
|
||||
->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, ?string $newKey, Response $response, Database $dbForProject, Event $queueForEvents) {
|
||||
$attribute = updateAttribute(
|
||||
databaseId: $databaseId,
|
||||
collectionId: $collectionId,
|
||||
|
|
@ -2205,7 +2272,8 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/datet
|
|||
queueForEvents: $queueForEvents,
|
||||
type: Database::VAR_DATETIME,
|
||||
default: $default,
|
||||
required: $required
|
||||
required: $required,
|
||||
newKey: $newKey
|
||||
);
|
||||
|
||||
$response
|
||||
|
|
@ -2230,6 +2298,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/:key/
|
|||
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
|
||||
->param('key', '', new Key(), 'Attribute Key.')
|
||||
->param('onDelete', null, new WhiteList([Database::RELATION_MUTATE_CASCADE, Database::RELATION_MUTATE_RESTRICT, Database::RELATION_MUTATE_SET_NULL], true), 'Constraints option', true)
|
||||
->param('newKey', null, new Key(), 'New attribute key.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
|
|
@ -2238,6 +2307,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/:key/
|
|||
string $collectionId,
|
||||
string $key,
|
||||
?string $onDelete,
|
||||
?string $newKey,
|
||||
Response $response,
|
||||
Database $dbForProject,
|
||||
Event $queueForEvents
|
||||
|
|
@ -2252,7 +2322,8 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/:key/
|
|||
required: false,
|
||||
options: [
|
||||
'onDelete' => $onDelete
|
||||
]
|
||||
],
|
||||
newKey: $newKey
|
||||
);
|
||||
|
||||
$options = $attribute->getAttribute('options', []);
|
||||
|
|
@ -2287,7 +2358,8 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/attributes/:key
|
|||
->inject('dbForProject')
|
||||
->inject('queueForDatabase')
|
||||
->inject('queueForEvents')
|
||||
->action(function (string $databaseId, string $collectionId, string $key, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents) {
|
||||
->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));
|
||||
|
||||
|
|
@ -2372,6 +2444,9 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/attributes/:key
|
|||
->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();
|
||||
});
|
||||
|
||||
|
|
@ -2435,7 +2510,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/indexes')
|
|||
'required' => true,
|
||||
'array' => false,
|
||||
'default' => null,
|
||||
'size' => 36
|
||||
'size' => Database::LENGTH_KEY
|
||||
];
|
||||
|
||||
$oldAttributes[] = [
|
||||
|
|
@ -2747,8 +2822,9 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents')
|
|||
->inject('dbForProject')
|
||||
->inject('user')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForUsage')
|
||||
->inject('mode')
|
||||
->action(function (string $databaseId, string $documentId, string $collectionId, string|array $data, ?array $permissions, Response $response, Database $dbForProject, Document $user, Event $queueForEvents, string $mode) {
|
||||
->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
|
||||
|
||||
|
|
@ -2964,6 +3040,9 @@ App::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
|
||||
});
|
||||
|
||||
App::get('/v1/databases/:databaseId/collections/:collectionId/documents')
|
||||
|
|
@ -3578,10 +3657,10 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:docu
|
|||
->inject('requestTimestamp')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForDeletes')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForUsage')
|
||||
->inject('mode')
|
||||
->action(function (string $databaseId, string $collectionId, string $documentId, ?\DateTime $requestTimestamp, Response $response, Database $dbForProject, Delete $queueForDeletes, Event $queueForEvents, string $mode) {
|
||||
->action(function (string $databaseId, string $collectionId, string $documentId, ?\DateTime $requestTimestamp, Response $response, Database $dbForProject, Event $queueForEvents, Usage $queueForUsage, string $mode) {
|
||||
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
|
||||
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
|
||||
|
|
@ -3654,10 +3733,6 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:docu
|
|||
)
|
||||
);
|
||||
|
||||
$queueForDeletes
|
||||
->setType(DELETE_TYPE_AUDIT)
|
||||
->setDocument($document);
|
||||
|
||||
$queueForEvents
|
||||
->setParam('databaseId', $databaseId)
|
||||
->setParam('collectionId', $collection->getId())
|
||||
|
|
@ -3666,6 +3741,9 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:docu
|
|||
->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();
|
||||
});
|
||||
|
||||
|
|
@ -3691,6 +3769,7 @@ App::get('/v1/databases/usage')
|
|||
METRIC_DATABASES,
|
||||
METRIC_COLLECTIONS,
|
||||
METRIC_DOCUMENTS,
|
||||
METRIC_DATABASES_STORAGE
|
||||
];
|
||||
|
||||
Authorization::skip(function () use ($dbForProject, $days, $metrics, &$stats) {
|
||||
|
|
@ -3741,9 +3820,11 @@ App::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);
|
||||
});
|
||||
|
||||
|
|
@ -3775,6 +3856,7 @@ App::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) {
|
||||
|
|
@ -3825,8 +3907,10 @@ App::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);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,10 @@ use Appwrite\Event\Usage;
|
|||
use Appwrite\Event\Validator\FunctionEvent;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Extend\Exception as AppwriteException;
|
||||
use Appwrite\Functions\Validator\Headers;
|
||||
use Appwrite\Functions\Validator\RuntimeSpecification;
|
||||
use Appwrite\Messaging\Adapter\Realtime;
|
||||
use Appwrite\Platform\Tasks\ScheduleExecutions;
|
||||
use Appwrite\Task\Validator\Cron;
|
||||
use Appwrite\Utopia\Database\Validator\CustomId;
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Deployments;
|
||||
|
|
@ -33,6 +36,7 @@ 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\Roles;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Storage\Device;
|
||||
|
|
@ -42,9 +46,11 @@ 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;
|
||||
|
|
@ -151,6 +157,7 @@ App::post('/v1/functions')
|
|||
->param('logging', true, new Boolean(), 'Whether executions will be logged. When set to false, executions will not be logged, but will reduce resource used by your Appwrite project.', true)
|
||||
->param('entrypoint', '', new Text(1028, 0), 'Entrypoint File. This path is relative to the "providerRootDirectory".', true)
|
||||
->param('commands', '', new Text(8192, 0), 'Build Commands.', true)
|
||||
->param('scopes', [], new ArrayList(new WhiteList(array_keys(Config::getParam('scopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'List of scopes allowed for API key auto-generated for every execution. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed.', true)
|
||||
->param('installationId', '', new Text(128, 0), 'Appwrite Installation ID for VCS (Version Control System) deployment.', true)
|
||||
->param('providerRepositoryId', '', new Text(128, 0), 'Repository ID of the repo linked to the function.', true)
|
||||
->param('providerBranch', '', new Text(128, 0), 'Production branch for the repo linked to the function.', true)
|
||||
|
|
@ -159,7 +166,13 @@ App::post('/v1/functions')
|
|||
->param('templateRepository', '', new Text(128, 0), 'Repository name of the template.', true)
|
||||
->param('templateOwner', '', new Text(128, 0), 'The name of the owner of the template.', true)
|
||||
->param('templateRootDirectory', '', new Text(128, 0), 'Path to function code in the template repo.', true)
|
||||
->param('templateBranch', '', new Text(128, 0), 'Production branch for the repo linked to the function template.', true)
|
||||
->param('templateVersion', '', new Text(128, 0), 'Version (tag) for the repo linked to the function template.', true)
|
||||
->param('specification', APP_FUNCTION_SPECIFICATION_DEFAULT, fn (array $plan) => new RuntimeSpecification(
|
||||
$plan,
|
||||
Config::getParam('runtime-specifications', []),
|
||||
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')
|
||||
->inject('dbForProject')
|
||||
|
|
@ -169,7 +182,7 @@ App::post('/v1/functions')
|
|||
->inject('queueForBuilds')
|
||||
->inject('dbForConsole')
|
||||
->inject('gitHub')
|
||||
->action(function (string $functionId, string $name, string $runtime, array $execute, array $events, string $schedule, int $timeout, bool $enabled, bool $logging, string $entrypoint, string $commands, string $installationId, string $providerRepositoryId, string $providerBranch, bool $providerSilentMode, string $providerRootDirectory, string $templateRepository, string $templateOwner, string $templateRootDirectory, string $templateBranch, Request $request, Response $response, Database $dbForProject, Document $project, Document $user, Event $queueForEvents, Build $queueForBuilds, Database $dbForConsole, GitHub $github) 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', '')));
|
||||
|
|
@ -184,12 +197,12 @@ App::post('/v1/functions')
|
|||
!empty($templateRepository)
|
||||
&& !empty($templateOwner)
|
||||
&& !empty($templateRootDirectory)
|
||||
&& !empty($templateBranch)
|
||||
&& !empty($templateVersion)
|
||||
) {
|
||||
$template->setAttribute('repositoryName', $templateRepository)
|
||||
->setAttribute('ownerName', $templateOwner)
|
||||
->setAttribute('rootDirectory', $templateRootDirectory)
|
||||
->setAttribute('branch', $templateBranch);
|
||||
->setAttribute('version', $templateVersion);
|
||||
}
|
||||
|
||||
$installation = $dbForConsole->getDocument('installations', $installationId);
|
||||
|
|
@ -219,8 +232,9 @@ App::post('/v1/functions')
|
|||
'timeout' => $timeout,
|
||||
'entrypoint' => $entrypoint,
|
||||
'commands' => $commands,
|
||||
'scopes' => $scopes,
|
||||
'search' => implode(' ', [$functionId, $name, $runtime]),
|
||||
'version' => 'v3',
|
||||
'version' => 'v4',
|
||||
'installationId' => $installation->getId(),
|
||||
'installationInternalId' => $installation->getInternalId(),
|
||||
'providerRepositoryId' => $providerRepositoryId,
|
||||
|
|
@ -229,6 +243,7 @@ App::post('/v1/functions')
|
|||
'providerBranch' => $providerBranch,
|
||||
'providerRootDirectory' => $providerRootDirectory,
|
||||
'providerSilentMode' => $providerSilentMode,
|
||||
'specification' => $specification
|
||||
]));
|
||||
|
||||
$schedule = Authorization::skip(
|
||||
|
|
@ -277,9 +292,34 @@ App::post('/v1/functions')
|
|||
|
||||
$function = $dbForProject->updateDocument('functions', $function->getId(), $function);
|
||||
|
||||
// Redeploy vcs logic
|
||||
if (!empty($providerRepositoryId)) {
|
||||
// Deploy VCS
|
||||
$redeployVcs($request, $function, $project, $installation, $dbForProject, $queueForBuilds, $template, $github);
|
||||
} elseif (!$template->isEmpty()) {
|
||||
// Deploy non-VCS from template
|
||||
$deploymentId = ID::unique();
|
||||
$deployment = $dbForProject->createDocument('deployments', new Document([
|
||||
'$id' => $deploymentId,
|
||||
'$permissions' => [
|
||||
Permission::read(Role::any()),
|
||||
Permission::update(Role::any()),
|
||||
Permission::delete(Role::any()),
|
||||
],
|
||||
'resourceId' => $function->getId(),
|
||||
'resourceInternalId' => $function->getInternalId(),
|
||||
'resourceType' => 'functions',
|
||||
'entrypoint' => $function->getAttribute('entrypoint', ''),
|
||||
'commands' => $function->getAttribute('commands', ''),
|
||||
'type' => 'manual',
|
||||
'search' => implode(' ', [$deploymentId, $function->getAttribute('entrypoint', '')]),
|
||||
'activate' => true,
|
||||
]));
|
||||
|
||||
$queueForBuilds
|
||||
->setType(BUILD_TYPE_DEPLOYMENT)
|
||||
->setResource($function)
|
||||
->setDeployment($deployment)
|
||||
->setTemplate($template);
|
||||
}
|
||||
|
||||
$functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', '');
|
||||
|
|
@ -427,13 +467,13 @@ App::get('/v1/functions/runtimes')
|
|||
$allowList = \array_filter(\explode(',', System::getEnv('_APP_FUNCTIONS_RUNTIMES', '')));
|
||||
|
||||
$allowed = [];
|
||||
foreach ($runtimes as $key => $runtime) {
|
||||
if (!empty($allowList) && !\in_array($key, $allowList)) {
|
||||
foreach ($runtimes as $id => $runtime) {
|
||||
if (!empty($allowList) && !\in_array($id, $allowList)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$runtimes[$key]['$id'] = $key;
|
||||
$allowed[] = $runtimes[$key];
|
||||
$runtime['$id'] = $id;
|
||||
$allowed[] = $runtime;
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([
|
||||
|
|
@ -442,6 +482,42 @@ App::get('/v1/functions/runtimes')
|
|||
]), Response::MODEL_RUNTIME_LIST);
|
||||
});
|
||||
|
||||
App::get('/v1/functions/specifications')
|
||||
->groups(['api', 'functions'])
|
||||
->desc('List available function runtime specifications')
|
||||
->label('scope', 'functions.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'functions')
|
||||
->label('sdk.method', 'listSpecifications')
|
||||
->label('sdk.description', '/docs/references/functions/list-specifications.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_SPECIFICATION_LIST)
|
||||
->inject('response')
|
||||
->inject('plan')
|
||||
->action(function (Response $response, array $plan) {
|
||||
$allRuntimeSpecs = Config::getParam('runtime-specifications', []);
|
||||
|
||||
$runtimeSpecs = [];
|
||||
foreach ($allRuntimeSpecs as $spec) {
|
||||
$spec['enabled'] = true;
|
||||
|
||||
if (array_key_exists('runtimeSpecifications', $plan)) {
|
||||
$spec['enabled'] = in_array($spec['slug'], $plan['runtimeSpecifications']);
|
||||
}
|
||||
|
||||
// Only add specs that are within the limits set by environment variables
|
||||
if ($spec['cpus'] <= System::getEnv('_APP_FUNCTIONS_CPUS', 1) && $spec['memory'] <= System::getEnv('_APP_FUNCTIONS_MEMORY', 512)) {
|
||||
$runtimeSpecs[] = $spec;
|
||||
}
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'specifications' => $runtimeSpecs,
|
||||
'total' => count($runtimeSpecs)
|
||||
]), Response::MODEL_SPECIFICATION_LIST);
|
||||
});
|
||||
|
||||
App::get('/v1/functions/:functionId')
|
||||
->groups(['api', 'functions'])
|
||||
->desc('Get function')
|
||||
|
|
@ -694,11 +770,18 @@ App::put('/v1/functions/:functionId')
|
|||
->param('logging', true, new Boolean(), 'Whether executions will be logged. When set to false, executions will not be logged, but will reduce resource used by your Appwrite project.', true)
|
||||
->param('entrypoint', '', new Text(1028, 0), 'Entrypoint File. This path is relative to the "providerRootDirectory".', true)
|
||||
->param('commands', '', new Text(8192, 0), 'Build Commands.', true)
|
||||
->param('scopes', [], new ArrayList(new WhiteList(array_keys(Config::getParam('scopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'List of scopes allowed for API Key auto-generated for every execution. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed.', true)
|
||||
->param('installationId', '', new Text(128, 0), 'Appwrite Installation ID for VCS (Version Controle System) deployment.', true)
|
||||
->param('providerRepositoryId', '', new Text(128, 0), 'Repository ID of the repo linked to the function', true)
|
||||
->param('providerRepositoryId', null, new Nullable(new Text(128, 0)), 'Repository ID of the repo linked to the function', true)
|
||||
->param('providerBranch', '', new Text(128, 0), 'Production branch for the repo linked to the function', true)
|
||||
->param('providerSilentMode', false, new Boolean(), 'Is the VCS (Version Control System) connection in silent mode for the repo linked to the function? In silent mode, comments will not be made on commits and pull requests.', true)
|
||||
->param('providerRootDirectory', '', new Text(128, 0), 'Path to function code in the linked repo.', true)
|
||||
->param('specification', APP_FUNCTION_SPECIFICATION_DEFAULT, fn (array $plan) => new RuntimeSpecification(
|
||||
$plan,
|
||||
Config::getParam('runtime-specifications', []),
|
||||
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')
|
||||
->inject('dbForProject')
|
||||
|
|
@ -707,9 +790,8 @@ App::put('/v1/functions/:functionId')
|
|||
->inject('queueForBuilds')
|
||||
->inject('dbForConsole')
|
||||
->inject('gitHub')
|
||||
->action(function (string $functionId, string $name, string $runtime, array $execute, array $events, string $schedule, int $timeout, bool $enabled, bool $logging, string $entrypoint, string $commands, string $installationId, string $providerRepositoryId, string $providerBranch, bool $providerSilentMode, string $providerRootDirectory, Request $request, Response $response, Database $dbForProject, Document $project, Event $queueForEvents, Build $queueForBuilds, Database $dbForConsole, GitHub $github) 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);
|
||||
|
||||
if ($function->isEmpty()) {
|
||||
|
|
@ -745,8 +827,8 @@ App::put('/v1/functions/:functionId')
|
|||
|
||||
$isConnected = !empty($function->getAttribute('providerRepositoryId', ''));
|
||||
|
||||
// Git disconnect logic
|
||||
if ($isConnected && empty($providerRepositoryId)) {
|
||||
// Git disconnect logic. Disconnecting only when providerRepositoryId is empty, allowing for continue updates without disconnecting git
|
||||
if ($isConnected && ($providerRepositoryId !== null && empty($providerRepositoryId))) {
|
||||
$repositories = $dbForConsole->find('repositories', [
|
||||
Query::equal('projectInternalId', [$project->getInternalId()]),
|
||||
Query::equal('resourceInternalId', [$function->getInternalId()]),
|
||||
|
|
@ -807,6 +889,21 @@ App::put('/v1/functions/:functionId')
|
|||
$live = false;
|
||||
}
|
||||
|
||||
$spec = Config::getParam('runtime-specifications')[$specification] ?? [];
|
||||
|
||||
// Enforce Cold Start if spec limits change.
|
||||
if ($function->getAttribute('specification') !== $specification && !empty($function->getAttribute('deployment'))) {
|
||||
$executor = new Executor(App::getEnv('_APP_EXECUTOR_HOST'));
|
||||
try {
|
||||
$executor->deleteRuntime($project->getId(), $function->getAttribute('deployment'));
|
||||
} catch (\Throwable $th) {
|
||||
// Don't throw if the deployment doesn't exist
|
||||
if ($th->getCode() !== 404) {
|
||||
throw $th;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$function = $dbForProject->updateDocument('functions', $function->getId(), new Document(array_merge($function->getArrayCopy(), [
|
||||
'execute' => $execute,
|
||||
'name' => $name,
|
||||
|
|
@ -819,6 +916,7 @@ App::put('/v1/functions/:functionId')
|
|||
'logging' => $logging,
|
||||
'entrypoint' => $entrypoint,
|
||||
'commands' => $commands,
|
||||
'scopes' => $scopes,
|
||||
'installationId' => $installation->getId(),
|
||||
'installationInternalId' => $installation->getInternalId(),
|
||||
'providerRepositoryId' => $providerRepositoryId,
|
||||
|
|
@ -827,6 +925,7 @@ App::put('/v1/functions/:functionId')
|
|||
'providerBranch' => $providerBranch,
|
||||
'providerRootDirectory' => $providerRootDirectory,
|
||||
'providerSilentMode' => $providerSilentMode,
|
||||
'specification' => $specification,
|
||||
'search' => implode(' ', [$functionId, $name, $runtime]),
|
||||
])));
|
||||
|
||||
|
|
@ -852,10 +951,10 @@ App::get('/v1/functions/:functionId/deployments/:deploymentId/download')
|
|||
->groups(['api', 'functions'])
|
||||
->desc('Download deployment')
|
||||
->label('scope', 'functions.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'functions')
|
||||
->label('sdk.method', 'downloadDeployment')
|
||||
->label('sdk.description', '/docs/references/functions/download-deployment.md')
|
||||
->label('sdk.method', 'getDeploymentDownload')
|
||||
->label('sdk.description', '/docs/references/functions/get-deployment-download.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', '*/*')
|
||||
->label('sdk.methodType', 'location')
|
||||
|
|
@ -1052,6 +1151,7 @@ App::post('/v1/functions/:functionId/deployments')
|
|||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'functions')
|
||||
->label('sdk.method', 'createDeployment')
|
||||
->label('sdk.methodType', 'upload')
|
||||
->label('sdk.description', '/docs/references/functions/create-deployment.md')
|
||||
->label('sdk.packaging', true)
|
||||
->label('sdk.request.type', 'multipart/form-data')
|
||||
|
|
@ -1071,9 +1171,9 @@ App::post('/v1/functions/:functionId/deployments')
|
|||
->inject('deviceForFunctions')
|
||||
->inject('deviceForLocal')
|
||||
->inject('queueForBuilds')
|
||||
->action(function (string $functionId, ?string $entrypoint, ?string $commands, mixed $code, bool $activate, Request $request, Response $response, Database $dbForProject, Event $queueForEvents, Document $project, Device $deviceForFunctions, Device $deviceForLocal, Build $queueForBuilds) {
|
||||
->action(function (string $functionId, ?string $entrypoint, ?string $commands, mixed $code, mixed $activate, Request $request, Response $response, Database $dbForProject, Event $queueForEvents, Document $project, Device $deviceForFunctions, Device $deviceForLocal, Build $queueForBuilds) {
|
||||
|
||||
$activate = filter_var($activate, FILTER_VALIDATE_BOOLEAN);
|
||||
$activate = \strval($activate) === 'true' || \strval($activate) === '1';
|
||||
|
||||
$function = $dbForProject->getDocument('functions', $functionId);
|
||||
|
||||
|
|
@ -1171,7 +1271,7 @@ App::post('/v1/functions/:functionId/deployments')
|
|||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed moving file');
|
||||
}
|
||||
|
||||
$activate = (bool) filter_var($activate, FILTER_VALIDATE_BOOLEAN);
|
||||
$type = $request->getHeader('x-sdk-language') === 'cli' ? 'cli' : 'manual';
|
||||
|
||||
if ($chunksUploaded === $chunks) {
|
||||
if ($activate) {
|
||||
|
|
@ -1209,7 +1309,7 @@ App::post('/v1/functions/:functionId/deployments')
|
|||
'search' => implode(' ', [$deploymentId, $entrypoint]),
|
||||
'activate' => $activate,
|
||||
'metadata' => $metadata,
|
||||
'type' => 'manual'
|
||||
'type' => $type
|
||||
]));
|
||||
} else {
|
||||
$deployment = $dbForProject->updateDocument('deployments', $deploymentId, $deployment->setAttribute('size', $fileSize)->setAttribute('metadata', $metadata));
|
||||
|
|
@ -1242,7 +1342,7 @@ App::post('/v1/functions/:functionId/deployments')
|
|||
'search' => implode(' ', [$deploymentId, $entrypoint]),
|
||||
'activate' => $activate,
|
||||
'metadata' => $metadata,
|
||||
'type' => 'manual'
|
||||
'type' => $type
|
||||
]));
|
||||
} else {
|
||||
$deployment = $dbForProject->updateDocument('deployments', $deploymentId, $deployment->setAttribute('chunksUploaded', $chunksUploaded)->setAttribute('metadata', $metadata));
|
||||
|
|
@ -1327,7 +1427,8 @@ App::get('/v1/functions/:functionId/deployments')
|
|||
$result->setAttribute('status', $build->getAttribute('status', 'processing'));
|
||||
$result->setAttribute('buildLogs', $build->getAttribute('logs', ''));
|
||||
$result->setAttribute('buildTime', $build->getAttribute('duration', 0));
|
||||
$result->setAttribute('size', $result->getAttribute('size', 0) + $build->getAttribute('size', 0));
|
||||
$result->setAttribute('buildSize', $build->getAttribute('size', 0));
|
||||
$result->setAttribute('size', $result->getAttribute('size', 0));
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([
|
||||
|
|
@ -1373,7 +1474,8 @@ App::get('/v1/functions/:functionId/deployments/:deploymentId')
|
|||
$deployment->setAttribute('status', $build->getAttribute('status', 'waiting'));
|
||||
$deployment->setAttribute('buildLogs', $build->getAttribute('logs', ''));
|
||||
$deployment->setAttribute('buildTime', $build->getAttribute('duration', 0));
|
||||
$deployment->setAttribute('size', $deployment->getAttribute('size', 0) + $build->getAttribute('size', 0));
|
||||
$deployment->setAttribute('buildSize', $build->getAttribute('size', 0));
|
||||
$deployment->setAttribute('size', $deployment->getAttribute('size', 0));
|
||||
|
||||
$response->dynamic($deployment, Response::MODEL_DEPLOYMENT);
|
||||
});
|
||||
|
|
@ -1442,9 +1544,10 @@ App::delete('/v1/functions/:functionId/deployments/:deploymentId')
|
|||
$response->noContent();
|
||||
});
|
||||
|
||||
App::post('/v1/functions/:functionId/deployments/:deploymentId/builds/:buildId')
|
||||
App::post('/v1/functions/:functionId/deployments/:deploymentId/build')
|
||||
->alias('/v1/functions/:functionId/deployments/:deploymentId/builds/:buildId')
|
||||
->groups(['api', 'functions'])
|
||||
->desc('Create build')
|
||||
->desc('Rebuild deployment')
|
||||
->label('scope', 'functions.write')
|
||||
->label('event', 'functions.[functionId].deployments.[deploymentId].update')
|
||||
->label('audits.event', 'deployment.update')
|
||||
|
|
@ -1452,45 +1555,47 @@ App::post('/v1/functions/:functionId/deployments/:deploymentId/builds/:buildId')
|
|||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'functions')
|
||||
->label('sdk.method', 'createBuild')
|
||||
->label('sdk.description', '/docs/references/functions/create-build.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
|
||||
->label('sdk.response.model', Response::MODEL_NONE)
|
||||
->param('functionId', '', new UID(), 'Function ID.')
|
||||
->param('deploymentId', '', new UID(), 'Deployment ID.')
|
||||
->param('buildId', '', new UID(), 'Build unique ID.')
|
||||
->param('buildId', '', new UID(), 'Build unique ID.', true) // added as optional param for backward compatibility
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('project')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForBuilds')
|
||||
->action(function (string $functionId, string $deploymentId, string $buildId, Request $request, Response $response, Database $dbForProject, Document $project, Event $queueForEvents, Build $queueForBuilds) {
|
||||
|
||||
->inject('deviceForFunctions')
|
||||
->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()) {
|
||||
throw new Exception(Exception::FUNCTION_NOT_FOUND);
|
||||
}
|
||||
|
||||
$deployment = $dbForProject->getDocument('deployments', $deploymentId);
|
||||
|
||||
if ($deployment->isEmpty()) {
|
||||
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$build = Authorization::skip(fn () => $dbForProject->getDocument('builds', $buildId));
|
||||
|
||||
if ($build->isEmpty()) {
|
||||
throw new Exception(Exception::BUILD_NOT_FOUND);
|
||||
$path = $deployment->getAttribute('path');
|
||||
if (empty($path) || !$deviceForFunctions->exists($path)) {
|
||||
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$deploymentId = ID::unique();
|
||||
|
||||
$destination = $deviceForFunctions->getPath($deploymentId . '.' . \pathinfo('code.tar.gz', PATHINFO_EXTENSION));
|
||||
$deviceForFunctions->transfer($path, $destination, $deviceForFunctions);
|
||||
|
||||
$deployment->removeAttribute('$internalId');
|
||||
$deployment = $dbForProject->createDocument('deployments', $deployment->setAttributes([
|
||||
'$internalId' => '',
|
||||
'$id' => $deploymentId,
|
||||
'buildId' => '',
|
||||
'buildInternalId' => '',
|
||||
'path' => $destination,
|
||||
'entrypoint' => $function->getAttribute('entrypoint'),
|
||||
'commands' => $function->getAttribute('commands', ''),
|
||||
'search' => implode(' ', [$deploymentId, $function->getAttribute('entrypoint')]),
|
||||
|
|
@ -1508,6 +1613,95 @@ App::post('/v1/functions/:functionId/deployments/:deploymentId/builds/:buildId')
|
|||
$response->noContent();
|
||||
});
|
||||
|
||||
App::patch('/v1/functions/:functionId/deployments/:deploymentId/build')
|
||||
->groups(['api', 'functions'])
|
||||
->desc('Cancel deployment')
|
||||
->label('scope', 'functions.write')
|
||||
->label('audits.event', 'deployment.update')
|
||||
->label('audits.resource', 'function/{request.functionId}')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'functions')
|
||||
->label('sdk.method', 'updateDeploymentBuild')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_BUILD)
|
||||
->param('functionId', '', new UID(), 'Function ID.')
|
||||
->param('deploymentId', '', new UID(), 'Deployment ID.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('project')
|
||||
->inject('queueForEvents')
|
||||
->action(function (string $functionId, string $deploymentId, Response $response, Database $dbForProject, Document $project, Event $queueForEvents) {
|
||||
$function = $dbForProject->getDocument('functions', $functionId);
|
||||
|
||||
if ($function->isEmpty()) {
|
||||
throw new Exception(Exception::FUNCTION_NOT_FOUND);
|
||||
}
|
||||
|
||||
$deployment = $dbForProject->getDocument('deployments', $deploymentId);
|
||||
|
||||
if ($deployment->isEmpty()) {
|
||||
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$build = Authorization::skip(fn () => $dbForProject->getDocument('builds', $deployment->getAttribute('buildId', '')));
|
||||
|
||||
if ($build->isEmpty()) {
|
||||
$buildId = ID::unique();
|
||||
$build = $dbForProject->createDocument('builds', new Document([
|
||||
'$id' => $buildId,
|
||||
'$permissions' => [],
|
||||
'startTime' => DateTime::now(),
|
||||
'deploymentInternalId' => $deployment->getInternalId(),
|
||||
'deploymentId' => $deployment->getId(),
|
||||
'status' => 'canceled',
|
||||
'path' => '',
|
||||
'runtime' => $function->getAttribute('runtime'),
|
||||
'source' => $deployment->getAttribute('path', ''),
|
||||
'sourceType' => '',
|
||||
'logs' => '',
|
||||
'duration' => 0,
|
||||
'size' => 0
|
||||
]));
|
||||
|
||||
$deployment->setAttribute('buildId', $build->getId());
|
||||
$deployment->setAttribute('buildInternalId', $build->getInternalId());
|
||||
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment);
|
||||
} else {
|
||||
if (\in_array($build->getAttribute('status'), ['ready', 'failed'])) {
|
||||
throw new Exception(Exception::BUILD_ALREADY_COMPLETED);
|
||||
}
|
||||
|
||||
$startTime = new \DateTime($build->getAttribute('startTime'));
|
||||
$endTime = new \DateTime('now');
|
||||
$duration = $endTime->getTimestamp() - $startTime->getTimestamp();
|
||||
|
||||
$build = $dbForProject->updateDocument('builds', $build->getId(), $build->setAttributes([
|
||||
'endTime' => DateTime::now(),
|
||||
'duration' => $duration,
|
||||
'status' => 'canceled'
|
||||
]));
|
||||
}
|
||||
|
||||
$dbForProject->purgeCachedDocument('deployments', $deployment->getId());
|
||||
|
||||
try {
|
||||
$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
|
||||
if ($th->getCode() !== 404) {
|
||||
throw $th;
|
||||
}
|
||||
}
|
||||
|
||||
$queueForEvents
|
||||
->setParam('functionId', $function->getId())
|
||||
->setParam('deploymentId', $deployment->getId());
|
||||
|
||||
$response->dynamic($build, Response::MODEL_BUILD);
|
||||
});
|
||||
|
||||
App::post('/v1/functions/:functionId/executions')
|
||||
->groups(['api', 'functions'])
|
||||
->desc('Create execution')
|
||||
|
|
@ -1518,24 +1712,55 @@ App::post('/v1/functions/:functionId/executions')
|
|||
->label('sdk.method', 'createExecution')
|
||||
->label('sdk.description', '/docs/references/functions/create-execution.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_MULTIPART)
|
||||
->label('sdk.response.model', Response::MODEL_EXECUTION)
|
||||
->label('sdk.request.type', Response::CONTENT_TYPE_JSON)
|
||||
->param('functionId', '', new UID(), 'Function ID.')
|
||||
->param('body', '', new Text(0, 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('body', '', new Text(10485760, 0), 'HTTP body of execution. Default value is empty string.', 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 Assoc(), 'HTTP headers of execution. Defaults to empty.', true)
|
||||
->param('headers', [], new AnyOf([new Assoc(), new Text(65535)], AnyOf::TYPE_MIXED), 'HTTP headers of execution. Defaults to empty.', true)
|
||||
->param('scheduledAt', null, new DatetimeValidator(true, DateTimeValidator::PRECISION_MINUTES, 60), 'Scheduled execution time in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. DateTime value must be in future with precision in minutes.', true)
|
||||
->inject('response')
|
||||
->inject('request')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForConsole')
|
||||
->inject('user')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForUsage')
|
||||
->inject('mode')
|
||||
->inject('queueForFunctions')
|
||||
->inject('geodb')
|
||||
->action(function (string $functionId, string $body, bool $async, string $path, string $method, array $headers, Response $response, Document $project, Database $dbForProject, Document $user, Event $queueForEvents, Usage $queueForUsage, string $mode, Func $queueForFunctions, Reader $geodb) {
|
||||
->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.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @var array<string, mixed> $headers
|
||||
*/
|
||||
$assocParams = ['headers'];
|
||||
foreach ($assocParams as $assocParam) {
|
||||
if (!empty('headers') && !is_array($$assocParam)) {
|
||||
$$assocParam = \json_decode($$assocParam, true);
|
||||
}
|
||||
}
|
||||
|
||||
$booleanParams = ['async'];
|
||||
foreach ($booleanParams as $booleamParam) {
|
||||
if (!empty($$booleamParam) && !is_bool($$booleamParam)) {
|
||||
$$booleamParam = $$booleamParam === "true" ? true : false;
|
||||
}
|
||||
}
|
||||
|
||||
// 'headers' validator
|
||||
$validator = new Headers();
|
||||
if (!$validator->isValid($headers)) {
|
||||
throw new Exception($validator->getDescription(), 400);
|
||||
}
|
||||
|
||||
$function = Authorization::skip(fn () => $dbForProject->getDocument('functions', $functionId));
|
||||
|
||||
|
|
@ -1548,6 +1773,7 @@ App::post('/v1/functions/:functionId/executions')
|
|||
|
||||
$version = $function->getAttribute('version', 'v2');
|
||||
$runtimes = Config::getParam($version === 'v2' ? 'runtimes-v2' : 'runtimes', []);
|
||||
$spec = Config::getParam('runtime-specifications')[$function->getAttribute('specification', APP_FUNCTION_SPECIFICATION_DEFAULT)];
|
||||
|
||||
$runtime = (isset($runtimes[$function->getAttribute('runtime', '')])) ? $runtimes[$function->getAttribute('runtime', '')] : null;
|
||||
|
||||
|
|
@ -1594,7 +1820,8 @@ App::post('/v1/functions/:functionId/executions')
|
|||
}
|
||||
|
||||
if (!$current->isEmpty()) {
|
||||
$jwtObj = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 900, 10); // Instantiate with key, algo, maxAge and leeway.
|
||||
$jwtExpiry = $function->getAttribute('timeout', 900);
|
||||
$jwtObj = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', $jwtExpiry, 0);
|
||||
$jwt = $jwtObj->encode([
|
||||
'userId' => $user->getId(),
|
||||
'sessionId' => $current->getId(),
|
||||
|
|
@ -1602,6 +1829,14 @@ App::post('/v1/functions/:functionId/executions')
|
|||
}
|
||||
}
|
||||
|
||||
$jwtExpiry = $function->getAttribute('timeout', 900);
|
||||
$jwtObj = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', $jwtExpiry, 0);
|
||||
$apiKey = $jwtObj->encode([
|
||||
'projectId' => $project->getId(),
|
||||
'scopes' => $function->getAttribute('scopes', [])
|
||||
]);
|
||||
|
||||
$headers['x-appwrite-key'] = API_KEY_DYNAMIC . '_' . $apiKey;
|
||||
$headers['x-appwrite-trigger'] = 'http';
|
||||
$headers['x-appwrite-user-id'] = $user->getId() ?? '';
|
||||
$headers['x-appwrite-user-jwt'] = $jwt ?? '';
|
||||
|
|
@ -1631,6 +1866,12 @@ App::post('/v1/functions/:functionId/executions')
|
|||
|
||||
$executionId = ID::unique();
|
||||
|
||||
$status = $async ? 'waiting' : 'processing';
|
||||
|
||||
if (!is_null($scheduledAt)) {
|
||||
$status = 'scheduled';
|
||||
}
|
||||
|
||||
$execution = new Document([
|
||||
'$id' => $executionId,
|
||||
'$permissions' => !$user->isEmpty() ? [Permission::read(Role::user($user->getId()))] : [],
|
||||
|
|
@ -1638,8 +1879,8 @@ App::post('/v1/functions/:functionId/executions')
|
|||
'functionId' => $function->getId(),
|
||||
'deploymentInternalId' => $deployment->getInternalId(),
|
||||
'deploymentId' => $deployment->getId(),
|
||||
'trigger' => 'http', // http / schedule / event
|
||||
'status' => $async ? 'waiting' : 'processing', // waiting / processing / completed / failed
|
||||
'trigger' => (!is_null($scheduledAt)) ? 'schedule' : 'http',
|
||||
'status' => $status, // waiting / processing / completed / failed / scheduled
|
||||
'responseStatusCode' => 0,
|
||||
'responseHeaders' => [],
|
||||
'requestPath' => $path,
|
||||
|
|
@ -1657,26 +1898,51 @@ App::post('/v1/functions/:functionId/executions')
|
|||
->setContext('function', $function);
|
||||
|
||||
if ($async) {
|
||||
if ($function->getAttribute('logging')) {
|
||||
/** @var Document $execution */
|
||||
if (is_null($scheduledAt)) {
|
||||
$execution = Authorization::skip(fn () => $dbForProject->createDocument('executions', $execution));
|
||||
$queueForFunctions
|
||||
->setType('http')
|
||||
->setExecution($execution)
|
||||
->setFunction($function)
|
||||
->setBody($body)
|
||||
->setHeaders($headers)
|
||||
->setPath($path)
|
||||
->setMethod($method)
|
||||
->setJWT($jwt)
|
||||
->setProject($project)
|
||||
->setUser($user)
|
||||
->setParam('functionId', $function->getId())
|
||||
->setParam('executionId', $execution->getId())
|
||||
->trigger();
|
||||
} else {
|
||||
$data = [
|
||||
'headers' => $headers,
|
||||
'path' => $path,
|
||||
'method' => $method,
|
||||
'body' => $body,
|
||||
'userId' => $user->getId()
|
||||
];
|
||||
|
||||
$schedule = $dbForConsole->createDocument('schedules', new Document([
|
||||
'region' => System::getEnv('_APP_REGION', 'default'),
|
||||
'resourceType' => ScheduleExecutions::getSupportedResource(),
|
||||
'resourceId' => $execution->getId(),
|
||||
'resourceInternalId' => $execution->getInternalId(),
|
||||
'resourceUpdatedAt' => DateTime::now(),
|
||||
'projectId' => $project->getId(),
|
||||
'schedule' => $scheduledAt,
|
||||
'data' => $data,
|
||||
'active' => true,
|
||||
]));
|
||||
|
||||
$execution = $execution
|
||||
->setAttribute('scheduleId', $schedule->getId())
|
||||
->setAttribute('scheduleInternalId', $schedule->getInternalId())
|
||||
->setAttribute('scheduledAt', $scheduledAt);
|
||||
|
||||
$execution = Authorization::skip(fn () => $dbForProject->createDocument('executions', $execution));
|
||||
}
|
||||
|
||||
$queueForFunctions
|
||||
->setType('http')
|
||||
->setExecution($execution)
|
||||
->setFunction($function)
|
||||
->setBody($body)
|
||||
->setHeaders($headers)
|
||||
->setPath($path)
|
||||
->setMethod($method)
|
||||
->setJWT($jwt)
|
||||
->setProject($project)
|
||||
->setUser($user)
|
||||
->setParam('functionId', $function->getId())
|
||||
->setParam('executionId', $execution->getId())
|
||||
->trigger();
|
||||
|
||||
return $response
|
||||
->setStatusCode(Response::STATUS_CODE_ACCEPTED)
|
||||
->dynamic($execution, Response::MODEL_EXECUTION);
|
||||
|
|
@ -1706,14 +1972,36 @@ App::post('/v1/functions/:functionId/executions')
|
|||
$vars[$var->getAttribute('key')] = $var->getAttribute('value', '');
|
||||
}
|
||||
|
||||
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https';
|
||||
$hostname = System::getEnv('_APP_DOMAIN');
|
||||
$endpoint = $protocol . '://' . $hostname . "/v1";
|
||||
|
||||
// Appwrite vars
|
||||
$vars = \array_merge($vars, [
|
||||
'APPWRITE_FUNCTION_API_ENDPOINT' => $endpoint,
|
||||
'APPWRITE_FUNCTION_ID' => $functionId,
|
||||
'APPWRITE_FUNCTION_NAME' => $function->getAttribute('name'),
|
||||
'APPWRITE_FUNCTION_DEPLOYMENT' => $deployment->getId(),
|
||||
'APPWRITE_FUNCTION_PROJECT_ID' => $project->getId(),
|
||||
'APPWRITE_FUNCTION_RUNTIME_NAME' => $runtime['name'] ?? '',
|
||||
'APPWRITE_FUNCTION_RUNTIME_VERSION' => $runtime['version'] ?? '',
|
||||
'APPWRITE_FUNCTION_CPUS' => $spec['cpus'] ?? APP_FUNCTION_CPUS_DEFAULT,
|
||||
'APPWRITE_FUNCTION_MEMORY' => $spec['memory'] ?? APP_FUNCTION_MEMORY_DEFAULT,
|
||||
'APPWRITE_VERSION' => APP_VERSION_STABLE,
|
||||
'APPWRITE_REGION' => $project->getAttribute('region'),
|
||||
'APPWRITE_DEPLOYMENT_TYPE' => $deployment->getAttribute('type', ''),
|
||||
'APPWRITE_VCS_REPOSITORY_ID' => $deployment->getAttribute('providerRepositoryId', ''),
|
||||
'APPWRITE_VCS_REPOSITORY_NAME' => $deployment->getAttribute('providerRepositoryName', ''),
|
||||
'APPWRITE_VCS_REPOSITORY_OWNER' => $deployment->getAttribute('providerRepositoryOwner', ''),
|
||||
'APPWRITE_VCS_REPOSITORY_URL' => $deployment->getAttribute('providerRepositoryUrl', ''),
|
||||
'APPWRITE_VCS_REPOSITORY_BRANCH' => $deployment->getAttribute('providerBranch', ''),
|
||||
'APPWRITE_VCS_REPOSITORY_BRANCH_URL' => $deployment->getAttribute('providerBranchUrl', ''),
|
||||
'APPWRITE_VCS_COMMIT_HASH' => $deployment->getAttribute('providerCommitHash', ''),
|
||||
'APPWRITE_VCS_COMMIT_MESSAGE' => $deployment->getAttribute('providerCommitMessage', ''),
|
||||
'APPWRITE_VCS_COMMIT_URL' => $deployment->getAttribute('providerCommitUrl', ''),
|
||||
'APPWRITE_VCS_COMMIT_AUTHOR_NAME' => $deployment->getAttribute('providerCommitAuthor', ''),
|
||||
'APPWRITE_VCS_COMMIT_AUTHOR_URL' => $deployment->getAttribute('providerCommitAuthorUrl', ''),
|
||||
'APPWRITE_VCS_ROOT_DIRECTORY' => $deployment->getAttribute('providerRootDirectory', ''),
|
||||
]);
|
||||
|
||||
/** Execute function */
|
||||
|
|
@ -1736,6 +2024,9 @@ App::post('/v1/functions/:functionId/executions')
|
|||
method: $method,
|
||||
headers: $headers,
|
||||
runtimeEntrypoint: $command,
|
||||
cpus: $spec['cpus'] ?? APP_FUNCTION_CPUS_DEFAULT,
|
||||
memory: $spec['memory'] ?? APP_FUNCTION_MEMORY_DEFAULT,
|
||||
logging: $function->getAttribute('logging', true),
|
||||
requestTimeout: 30
|
||||
);
|
||||
|
||||
|
|
@ -1747,7 +2038,7 @@ App::post('/v1/functions/:functionId/executions')
|
|||
}
|
||||
|
||||
/** Update execution status */
|
||||
$status = $executionResponse['statusCode'] >= 400 ? 'failed' : 'completed';
|
||||
$status = $executionResponse['statusCode'] >= 500 ? 'failed' : 'completed';
|
||||
$execution->setAttribute('status', $status);
|
||||
$execution->setAttribute('responseStatusCode', $executionResponse['statusCode']);
|
||||
$execution->setAttribute('responseHeaders', $headersFiltered);
|
||||
|
|
@ -1773,14 +2064,11 @@ App::post('/v1/functions/:functionId/executions')
|
|||
->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS), 1)
|
||||
->addMetric(METRIC_EXECUTIONS_COMPUTE, (int)($execution->getAttribute('duration') * 1000)) // per project
|
||||
->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS_COMPUTE), (int)($execution->getAttribute('duration') * 1000)) // per function
|
||||
->addMetric(METRIC_EXECUTIONS_MB_SECONDS, (int)(512 * $execution->getAttribute('duration', 0)))
|
||||
->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS_MB_SECONDS), (int)(512 * $execution->getAttribute('duration', 0)))
|
||||
->addMetric(METRIC_EXECUTIONS_MB_SECONDS, (int)(($spec['memory'] ?? APP_FUNCTION_MEMORY_DEFAULT) * $execution->getAttribute('duration', 0) * ($spec['cpus'] ?? APP_FUNCTION_CPUS_DEFAULT)))
|
||||
->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)))
|
||||
;
|
||||
|
||||
if ($function->getAttribute('logging')) {
|
||||
/** @var Document $execution */
|
||||
$execution = Authorization::skip(fn () => $dbForProject->createDocument('executions', $execution));
|
||||
}
|
||||
$execution = Authorization::skip(fn () => $dbForProject->createDocument('executions', $execution));
|
||||
}
|
||||
|
||||
$roles = Authorization::getRoles();
|
||||
|
|
@ -1800,6 +2088,17 @@ App::post('/v1/functions/:functionId/executions')
|
|||
$execution->setAttribute('responseBody', $executionResponse['body'] ?? '');
|
||||
$execution->setAttribute('responseHeaders', $headers);
|
||||
|
||||
$acceptTypes = \explode(', ', $request->getHeader('accept'));
|
||||
foreach ($acceptTypes as $acceptType) {
|
||||
if (\str_starts_with($acceptType, 'application/json') || \str_starts_with($acceptType, 'application/*')) {
|
||||
$response->setContentType(Response::CONTENT_TYPE_JSON);
|
||||
break;
|
||||
} elseif (\str_starts_with($acceptType, 'multipart/form-data') || \str_starts_with($acceptType, 'multipart/*')) {
|
||||
$response->setContentType(Response::CONTENT_TYPE_MULTIPART);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
->dynamic($execution, Response::MODEL_EXECUTION);
|
||||
|
|
@ -1933,6 +2232,74 @@ App::get('/v1/functions/:functionId/executions/:executionId')
|
|||
$response->dynamic($execution, Response::MODEL_EXECUTION);
|
||||
});
|
||||
|
||||
App::delete('/v1/functions/:functionId/executions/:executionId')
|
||||
->groups(['api', 'functions'])
|
||||
->desc('Delete execution')
|
||||
->label('scope', 'execution.write')
|
||||
->label('event', 'functions.[functionId].executions.[executionId].delete')
|
||||
->label('audits.event', 'executions.delete')
|
||||
->label('audits.resource', 'function/{request.functionId}')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'functions')
|
||||
->label('sdk.method', 'deleteExecution')
|
||||
->label('sdk.description', '/docs/references/functions/delete-execution.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
|
||||
->label('sdk.response.model', Response::MODEL_NONE)
|
||||
->param('functionId', '', new UID(), 'Function ID.')
|
||||
->param('executionId', '', new UID(), 'Execution ID.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForConsole')
|
||||
->inject('queueForEvents')
|
||||
->action(function (string $functionId, string $executionId, Response $response, Database $dbForProject, Database $dbForConsole, Event $queueForEvents) {
|
||||
$function = $dbForProject->getDocument('functions', $functionId);
|
||||
|
||||
if ($function->isEmpty()) {
|
||||
throw new Exception(Exception::FUNCTION_NOT_FOUND);
|
||||
}
|
||||
|
||||
$execution = $dbForProject->getDocument('executions', $executionId);
|
||||
if ($execution->isEmpty()) {
|
||||
throw new Exception(Exception::EXECUTION_NOT_FOUND);
|
||||
}
|
||||
|
||||
if ($execution->getAttribute('functionId') !== $function->getId()) {
|
||||
throw new Exception(Exception::EXECUTION_NOT_FOUND);
|
||||
}
|
||||
$status = $execution->getAttribute('status');
|
||||
|
||||
if (!in_array($status, ['completed', 'failed', 'scheduled'])) {
|
||||
throw new Exception(Exception::EXECUTION_IN_PROGRESS);
|
||||
}
|
||||
|
||||
if (!$dbForProject->deleteDocument('executions', $execution->getId())) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove execution from DB');
|
||||
}
|
||||
|
||||
if ($status === 'scheduled') {
|
||||
$schedule = $dbForConsole->findOne('schedules', [
|
||||
Query::equal('resourceId', [$execution->getId()]),
|
||||
Query::equal('resourceType', [ScheduleExecutions::getSupportedResource()]),
|
||||
Query::equal('active', [true]),
|
||||
]);
|
||||
|
||||
if ($schedule && !$schedule->isEmpty()) {
|
||||
$schedule
|
||||
->setAttribute('resourceUpdatedAt', DateTime::now())
|
||||
->setAttribute('active', false);
|
||||
|
||||
Authorization::skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule));
|
||||
}
|
||||
}
|
||||
|
||||
$queueForEvents
|
||||
->setParam('functionId', $function->getId())
|
||||
->setParam('executionId', $execution->getId())
|
||||
->setPayload($response->output($execution, Response::MODEL_EXECUTION));
|
||||
|
||||
$response->noContent();
|
||||
});
|
||||
|
||||
// Variables
|
||||
|
||||
App::post('/v1/functions/:functionId/variables')
|
||||
|
|
@ -2173,3 +2540,67 @@ App::delete('/v1/functions/:functionId/variables/:variableId')
|
|||
|
||||
$response->noContent();
|
||||
});
|
||||
|
||||
App::get('/v1/functions/templates')
|
||||
->groups(['api'])
|
||||
->desc('List function templates')
|
||||
->label('scope', 'public')
|
||||
->label('sdk.namespace', 'functions')
|
||||
->label('sdk.method', 'listTemplates')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.description', '/docs/references/functions/list-templates.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_TEMPLATE_FUNCTION_LIST)
|
||||
->param('runtimes', [], new ArrayList(new WhiteList(array_keys(Config::getParam('runtimes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'List of runtimes allowed for filtering function templates. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' runtimes are allowed.', true)
|
||||
->param('useCases', [], new ArrayList(new WhiteList(['dev-tools','starter','databases','ai','messaging','utilities']), APP_LIMIT_ARRAY_PARAMS_SIZE), 'List of use cases allowed for filtering function templates. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' use cases are allowed.', true)
|
||||
->param('limit', 25, new Range(1, 5000), 'Limit the number of templates returned in the response. Default limit is 25, and maximum limit is 5000.', true)
|
||||
->param('offset', 0, new Range(0, 5000), 'Offset the list of returned templates. Maximum offset is 5000.', true)
|
||||
->inject('response')
|
||||
->action(function (array $runtimes, array $usecases, int $limit, int $offset, Response $response) {
|
||||
$templates = Config::getParam('function-templates', []);
|
||||
|
||||
if (!empty($runtimes)) {
|
||||
$templates = \array_filter($templates, function ($template) use ($runtimes) {
|
||||
return \count(\array_intersect($runtimes, \array_column($template['runtimes'], 'name'))) > 0;
|
||||
});
|
||||
}
|
||||
|
||||
if (!empty($usecases)) {
|
||||
$templates = \array_filter($templates, function ($template) use ($usecases) {
|
||||
return \count(\array_intersect($usecases, $template['useCases'])) > 0;
|
||||
});
|
||||
}
|
||||
|
||||
$responseTemplates = \array_slice($templates, $offset, $limit);
|
||||
$response->dynamic(new Document([
|
||||
'templates' => $responseTemplates,
|
||||
'total' => \count($responseTemplates),
|
||||
]), Response::MODEL_TEMPLATE_FUNCTION_LIST);
|
||||
});
|
||||
|
||||
App::get('/v1/functions/templates/:templateId')
|
||||
->desc('Get function template')
|
||||
->label('scope', 'public')
|
||||
->label('sdk.namespace', 'functions')
|
||||
->label('sdk.method', 'getTemplate')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.description', '/docs/references/functions/get-template.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_TEMPLATE_FUNCTION)
|
||||
->param('templateId', '', new Text(128), 'Template ID.')
|
||||
->inject('response')
|
||||
->action(function (string $templateId, Response $response) {
|
||||
$templates = Config::getParam('function-templates', []);
|
||||
|
||||
$template = array_shift(\array_filter($templates, function ($template) use ($templateId) {
|
||||
return $template['id'] === $templateId;
|
||||
}));
|
||||
|
||||
if (empty($template)) {
|
||||
throw new Exception(Exception::FUNCTION_TEMPLATE_NOT_FOUND);
|
||||
}
|
||||
|
||||
$response->dynamic(new Document($template), Response::MODEL_TEMPLATE_FUNCTION);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ App::get('/v1/locale')
|
|||
});
|
||||
|
||||
App::get('/v1/locale/codes')
|
||||
->desc('List 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])
|
||||
|
|
|
|||
|
|
@ -2697,7 +2697,7 @@ App::post('/v1/messaging/messages/email')
|
|||
'resourceInternalId' => $message->getInternalId(),
|
||||
'resourceUpdatedAt' => DateTime::now(),
|
||||
'projectId' => $project->getId(),
|
||||
'schedule' => $scheduledAt,
|
||||
'schedule' => $scheduledAt,
|
||||
'active' => true,
|
||||
]));
|
||||
|
||||
|
|
@ -2813,7 +2813,7 @@ App::post('/v1/messaging/messages/sms')
|
|||
'resourceInternalId' => $message->getInternalId(),
|
||||
'resourceUpdatedAt' => DateTime::now(),
|
||||
'projectId' => $project->getId(),
|
||||
'schedule' => $scheduledAt,
|
||||
'schedule' => $scheduledAt,
|
||||
'active' => true,
|
||||
]));
|
||||
|
||||
|
|
@ -2939,11 +2939,9 @@ App::post('/v1/messaging/messages/push')
|
|||
$expiry = (new \DateTime())->add(new \DateInterval('P15D'))->format('U');
|
||||
}
|
||||
|
||||
$encoder = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'));
|
||||
$encoder = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', \intval($expiry), 0);
|
||||
|
||||
$jwt = $encoder->encode([
|
||||
'iat' => \time(),
|
||||
'exp' => $expiry,
|
||||
'bucketId' => $bucket->getId(),
|
||||
'fileId' => $file->getId(),
|
||||
'projectId' => $project->getId(),
|
||||
|
|
@ -2991,7 +2989,7 @@ App::post('/v1/messaging/messages/push')
|
|||
'resourceInternalId' => $message->getInternalId(),
|
||||
'resourceUpdatedAt' => DateTime::now(),
|
||||
'projectId' => $project->getId(),
|
||||
'schedule' => $scheduledAt,
|
||||
'schedule' => $scheduledAt,
|
||||
'active' => true,
|
||||
]));
|
||||
|
||||
|
|
@ -3801,11 +3799,9 @@ App::patch('/v1/messaging/messages/push/:messageId')
|
|||
$expiry = (new \DateTime())->add(new \DateInterval('P15D'))->format('U');
|
||||
}
|
||||
|
||||
$encoder = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'));
|
||||
$encoder = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', \intval($expiry), 0);
|
||||
|
||||
$jwt = $encoder->encode([
|
||||
'iat' => \time(),
|
||||
'exp' => $expiry,
|
||||
'bucketId' => $bucket->getId(),
|
||||
'fileId' => $file->getId(),
|
||||
'projectId' => $project->getId(),
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ include_once __DIR__ . '/../shared/api.php';
|
|||
|
||||
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 @@ App::post('/v1/migrations/appwrite')
|
|||
'status' => 'pending',
|
||||
'stage' => 'init',
|
||||
'source' => Appwrite::getName(),
|
||||
'destination' => Appwrite::getName(),
|
||||
'credentials' => [
|
||||
'endpoint' => $endpoint,
|
||||
'projectId' => $projectId,
|
||||
|
|
@ -87,7 +88,7 @@ App::post('/v1/migrations/appwrite')
|
|||
|
||||
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 @@ App::post('/v1/migrations/firebase/oauth')
|
|||
'status' => 'pending',
|
||||
'stage' => 'init',
|
||||
'source' => Firebase::getName(),
|
||||
'destination' => Appwrite::getName(),
|
||||
'credentials' => [
|
||||
'serviceAccount' => json_encode($serviceAccount),
|
||||
],
|
||||
|
|
@ -189,7 +191,7 @@ App::post('/v1/migrations/firebase/oauth')
|
|||
|
||||
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 @@ App::post('/v1/migrations/firebase')
|
|||
'status' => 'pending',
|
||||
'stage' => 'init',
|
||||
'source' => Firebase::getName(),
|
||||
'destination' => Appwrite::getName(),
|
||||
'credentials' => [
|
||||
'serviceAccount' => $serviceAccount,
|
||||
],
|
||||
|
|
@ -249,7 +252,7 @@ App::post('/v1/migrations/firebase')
|
|||
|
||||
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 @@ App::post('/v1/migrations/supabase')
|
|||
'status' => 'pending',
|
||||
'stage' => 'init',
|
||||
'source' => Supabase::getName(),
|
||||
'destination' => Appwrite::getName(),
|
||||
'credentials' => [
|
||||
'endpoint' => $endpoint,
|
||||
'apiKey' => $apiKey,
|
||||
|
|
@ -309,7 +313,7 @@ App::post('/v1/migrations/supabase')
|
|||
|
||||
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 @@ App::post('/v1/migrations/nhost')
|
|||
'status' => 'pending',
|
||||
'stage' => 'init',
|
||||
'source' => NHost::getName(),
|
||||
'destination' => Appwrite::getName(),
|
||||
'credentials' => [
|
||||
'subdomain' => $subdomain,
|
||||
'region' => $region,
|
||||
|
|
@ -371,7 +376,7 @@ App::post('/v1/migrations/nhost')
|
|||
|
||||
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')
|
||||
|
|
@ -424,7 +429,7 @@ App::get('/v1/migrations')
|
|||
|
||||
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')
|
||||
|
|
@ -448,7 +453,7 @@ App::get('/v1/migrations/:migrationId')
|
|||
|
||||
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')
|
||||
|
|
@ -490,7 +495,7 @@ App::get('/v1/migrations/appwrite/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')
|
||||
|
|
@ -537,7 +542,7 @@ App::get('/v1/migrations/firebase/report')
|
|||
|
||||
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')
|
||||
|
|
@ -627,7 +632,7 @@ App::get('/v1/migrations/firebase/report/oauth')
|
|||
});
|
||||
|
||||
App::get('/v1/migrations/firebase/connect')
|
||||
->desc('Authorize with firebase')
|
||||
->desc('Authorize with Firebase')
|
||||
->groups(['api', 'migrations'])
|
||||
->label('scope', 'migrations.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
|
|
@ -781,7 +786,7 @@ App::get('/v1/migrations/firebase/redirect')
|
|||
});
|
||||
|
||||
App::get('/v1/migrations/firebase/projects')
|
||||
->desc('List Firebase Projects')
|
||||
->desc('List Firebase projects')
|
||||
->groups(['api', 'migrations'])
|
||||
->label('scope', 'migrations.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
|
|
@ -870,7 +875,7 @@ App::get('/v1/migrations/firebase/projects')
|
|||
});
|
||||
|
||||
App::get('/v1/migrations/firebase/deauthorize')
|
||||
->desc('Revoke Appwrite\'s authorization to access Firebase Projects')
|
||||
->desc('Revoke Appwrite\'s authorization to access Firebase projects')
|
||||
->groups(['api', 'migrations'])
|
||||
->label('scope', 'migrations.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
|
|
@ -985,7 +990,7 @@ App::get('/v1/migrations/nhost/report')
|
|||
|
||||
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')
|
||||
|
|
@ -1030,7 +1035,7 @@ App::patch('/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')
|
||||
|
|
|
|||
|
|
@ -47,7 +47,9 @@ App::get('/v1/project/usage')
|
|||
METRIC_USERS,
|
||||
METRIC_BUCKETS,
|
||||
METRIC_FILES_STORAGE,
|
||||
METRIC_DEPLOYMENTS_STORAGE
|
||||
METRIC_DATABASES_STORAGE,
|
||||
METRIC_DEPLOYMENTS_STORAGE,
|
||||
METRIC_BUILDS_STORAGE
|
||||
],
|
||||
'period' => [
|
||||
METRIC_NETWORK_REQUESTS,
|
||||
|
|
@ -55,6 +57,7 @@ App::get('/v1/project/usage')
|
|||
METRIC_NETWORK_OUTBOUND,
|
||||
METRIC_USERS,
|
||||
METRIC_EXECUTIONS,
|
||||
METRIC_DATABASES_STORAGE,
|
||||
METRIC_EXECUTIONS_MB_SECONDS,
|
||||
METRIC_BUILDS_MB_SECONDS
|
||||
]
|
||||
|
|
@ -133,6 +136,38 @@ App::get('/v1/project/usage')
|
|||
];
|
||||
}, $dbForProject->find('functions'));
|
||||
|
||||
$executionsMbSecondsBreakdown = array_map(function ($function) use ($dbForProject) {
|
||||
$id = $function->getId();
|
||||
$name = $function->getAttribute('name');
|
||||
$metric = str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS_MB_SECONDS);
|
||||
$value = $dbForProject->findOne('stats', [
|
||||
Query::equal('metric', [$metric]),
|
||||
Query::equal('period', ['inf'])
|
||||
]);
|
||||
|
||||
return [
|
||||
'resourceId' => $id,
|
||||
'name' => $name,
|
||||
'value' => $value['value'] ?? 0,
|
||||
];
|
||||
}, $dbForProject->find('functions'));
|
||||
|
||||
$buildsMbSecondsBreakdown = array_map(function ($function) use ($dbForProject) {
|
||||
$id = $function->getId();
|
||||
$name = $function->getAttribute('name');
|
||||
$metric = str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_BUILDS_MB_SECONDS);
|
||||
$value = $dbForProject->findOne('stats', [
|
||||
Query::equal('metric', [$metric]),
|
||||
Query::equal('period', ['inf'])
|
||||
]);
|
||||
|
||||
return [
|
||||
'resourceId' => $id,
|
||||
'name' => $name,
|
||||
'value' => $value['value'] ?? 0,
|
||||
];
|
||||
}, $dbForProject->find('functions'));
|
||||
|
||||
$bucketsBreakdown = array_map(function ($bucket) use ($dbForProject) {
|
||||
$id = $bucket->getId();
|
||||
$name = $bucket->getAttribute('name');
|
||||
|
|
@ -149,10 +184,11 @@ App::get('/v1/project/usage')
|
|||
];
|
||||
}, $dbForProject->find('buckets'));
|
||||
|
||||
$deploymentsStorageBreakdown = array_map(function ($function) use ($dbForProject) {
|
||||
$id = $function->getId();
|
||||
$name = $function->getAttribute('name');
|
||||
$metric = str_replace(['{resourceType}', '{resourceInternalId}'], ['functions', $function->getInternalId()], METRIC_FUNCTION_ID_DEPLOYMENTS_STORAGE);
|
||||
$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'])
|
||||
|
|
@ -163,6 +199,30 @@ App::get('/v1/project/usage')
|
|||
'name' => $name,
|
||||
'value' => $value['value'] ?? 0,
|
||||
];
|
||||
}, $dbForProject->find('databases'));
|
||||
|
||||
$functionsStorageBreakdown = array_map(function ($function) use ($dbForProject) {
|
||||
$id = $function->getId();
|
||||
$name = $function->getAttribute('name');
|
||||
$deploymentMetric = str_replace(['{resourceType}', '{resourceInternalId}'], ['functions', $function->getInternalId()], METRIC_FUNCTION_ID_DEPLOYMENTS_STORAGE);
|
||||
$deploymentValue = $dbForProject->findOne('stats', [
|
||||
Query::equal('metric', [$deploymentMetric]),
|
||||
Query::equal('period', ['inf'])
|
||||
]);
|
||||
|
||||
$buildMetric = str_replace(['{functionInternalId}'], [$function->getInternalId()], METRIC_FUNCTION_ID_BUILDS_STORAGE);
|
||||
$buildValue = $dbForProject->findOne('stats', [
|
||||
Query::equal('metric', [$buildMetric]),
|
||||
Query::equal('period', ['inf'])
|
||||
]);
|
||||
|
||||
$value = ($buildValue['value'] ?? 0) + ($deploymentValue['value'] ?? 0);
|
||||
|
||||
return [
|
||||
'resourceId' => $id,
|
||||
'name' => $name,
|
||||
'value' => $value,
|
||||
];
|
||||
}, $dbForProject->find('functions'));
|
||||
|
||||
$executionsMbSecondsBreakdown = array_map(function ($function) use ($dbForProject) {
|
||||
|
|
@ -228,22 +288,28 @@ App::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],
|
||||
'functionsStorageTotal' => $total[METRIC_DEPLOYMENTS_STORAGE] + $total[METRIC_BUILDS_STORAGE],
|
||||
'buildsStorageTotal' => $total[METRIC_BUILDS_STORAGE],
|
||||
'deploymentsStorageTotal' => $total[METRIC_DEPLOYMENTS_STORAGE],
|
||||
'executionsBreakdown' => $executionsBreakdown,
|
||||
'executionsMbSecondsBreakdown' => $executionsMbSecondsBreakdown,
|
||||
'buildsMbSecondsBreakdown' => $buildsMbSecondsBreakdown,
|
||||
'bucketsBreakdown' => $bucketsBreakdown,
|
||||
'deploymentsStorageBreakdown' => $deploymentsStorageBreakdown,
|
||||
'databasesStorageBreakdown' => $databasesStorageBreakdown,
|
||||
'executionsMbSecondsBreakdown' => $executionsMbSecondsBreakdown,
|
||||
'buildsMbSecondsBreakdown' => $buildsMbSecondsBreakdown,
|
||||
'functionsStorageBreakdown' => $functionsStorageBreakdown,
|
||||
]), Response::MODEL_USAGE_PROJECT);
|
||||
});
|
||||
|
||||
|
||||
// Variables
|
||||
App::post('/v1/project/variables')
|
||||
->desc('Create Variable')
|
||||
->desc('Create variable')
|
||||
->groups(['api'])
|
||||
->label('scope', 'projects.write')
|
||||
->label('audits.event', 'variable.create')
|
||||
|
|
@ -298,7 +364,7 @@ App::post('/v1/project/variables')
|
|||
});
|
||||
|
||||
App::get('/v1/project/variables')
|
||||
->desc('List Variables')
|
||||
->desc('List variables')
|
||||
->groups(['api'])
|
||||
->label('scope', 'projects.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
|
|
@ -323,7 +389,7 @@ App::get('/v1/project/variables')
|
|||
});
|
||||
|
||||
App::get('/v1/project/variables/:variableId')
|
||||
->desc('Get Variable')
|
||||
->desc('Get variable')
|
||||
->groups(['api'])
|
||||
->label('scope', 'projects.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
|
|
@ -347,7 +413,7 @@ App::get('/v1/project/variables/:variableId')
|
|||
});
|
||||
|
||||
App::put('/v1/project/variables/:variableId')
|
||||
->desc('Update Variable')
|
||||
->desc('Update variable')
|
||||
->groups(['api'])
|
||||
->label('scope', 'projects.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
|
|
@ -393,7 +459,7 @@ App::put('/v1/project/variables/:variableId')
|
|||
});
|
||||
|
||||
App::delete('/v1/project/variables/:variableId')
|
||||
->desc('Delete Variable')
|
||||
->desc('Delete variable')
|
||||
->groups(['api'])
|
||||
->label('scope', 'projects.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
<?php
|
||||
|
||||
use Ahc\Jwt\JWT;
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Auth\Validator\MockNumber;
|
||||
use Appwrite\Event\Delete;
|
||||
use Appwrite\Event\Mail;
|
||||
use Appwrite\Event\Validator\Event;
|
||||
|
|
@ -20,6 +22,7 @@ use Utopia\Audit\Audit;
|
|||
use Utopia\Cache\Cache;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Duplicate;
|
||||
use Utopia\Database\Exception\Query as QueryException;
|
||||
|
|
@ -104,8 +107,11 @@ App::post('/v1/projects')
|
|||
'passwordHistory' => 0,
|
||||
'passwordDictionary' => false,
|
||||
'duration' => Auth::TOKEN_EXPIRATION_LOGIN_LONG,
|
||||
'personalDataCheck' => false
|
||||
'personalDataCheck' => false,
|
||||
'mockNumbers' => [],
|
||||
'sessionAlerts' => false,
|
||||
];
|
||||
|
||||
foreach ($auth as $method) {
|
||||
$auths[$method['key'] ?? ''] = true;
|
||||
}
|
||||
|
|
@ -170,6 +176,7 @@ App::post('/v1/projects')
|
|||
'webhooks' => null,
|
||||
'keys' => null,
|
||||
'auths' => $auths,
|
||||
'accessedAt' => DateTime::now(),
|
||||
'search' => implode(' ', [$projectId, $name]),
|
||||
'database' => $dsn,
|
||||
]));
|
||||
|
|
@ -369,7 +376,7 @@ App::patch('/v1/projects/:projectId')
|
|||
});
|
||||
|
||||
App::patch('/v1/projects/:projectId/team')
|
||||
->desc('Update Project Team')
|
||||
->desc('Update project team')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
|
|
@ -610,6 +617,37 @@ App::patch('/v1/projects/:projectId/oauth2')
|
|||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
});
|
||||
|
||||
App::patch('/v1/projects/:projectId/auth/session-alerts')
|
||||
->desc('Update project sessions emails')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'projects')
|
||||
->label('sdk.method', 'updateSessionAlerts')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_PROJECT)
|
||||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
->param('alerts', false, new Boolean(true), 'Set to true to enable session emails.')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, bool $alerts, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$auths = $project->getAttribute('auths', []);
|
||||
$auths['sessionAlerts'] = $alerts;
|
||||
|
||||
$dbForConsole->updateDocument('projects', $project->getId(), $project
|
||||
->setAttribute('auths', $auths));
|
||||
|
||||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
});
|
||||
|
||||
App::patch('/v1/projects/:projectId/auth/limit')
|
||||
->desc('Update project users limit')
|
||||
->groups(['api', 'projects'])
|
||||
|
|
@ -830,6 +868,45 @@ App::patch('/v1/projects/:projectId/auth/max-sessions')
|
|||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
});
|
||||
|
||||
App::patch('/v1/projects/:projectId/auth/mock-numbers')
|
||||
->desc('Update the mock numbers for the project')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'projects')
|
||||
->label('sdk.method', 'updateMockNumbers')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_PROJECT)
|
||||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
->param('numbers', '', new ArrayList(new MockNumber(), 10), 'An array of mock numbers and their corresponding verification codes (OTPs). Each number should be a valid E.164 formatted phone number. Maximum of 10 numbers are allowed.')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, array $numbers, Response $response, Database $dbForConsole) {
|
||||
|
||||
$uniqueNumbers = [];
|
||||
foreach ($numbers as $number) {
|
||||
if (isset($uniqueNumbers[$number['phone']])) {
|
||||
throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Duplicate phone numbers are not allowed.');
|
||||
}
|
||||
$uniqueNumbers[$number['phone']] = $number['otp'];
|
||||
}
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$auths = $project->getAttribute('auths', []);
|
||||
|
||||
$auths['mockNumbers'] = $numbers;
|
||||
|
||||
$project = $dbForConsole->updateDocument('projects', $project->getId(), $project->setAttribute('auths', $auths));
|
||||
|
||||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
});
|
||||
|
||||
App::delete('/v1/projects/:projectId')
|
||||
->desc('Delete project')
|
||||
->groups(['api', 'projects'])
|
||||
|
|
@ -853,6 +930,7 @@ App::delete('/v1/projects/:projectId')
|
|||
}
|
||||
|
||||
$queueForDeletes
|
||||
->setProject($project)
|
||||
->setType(DELETE_TYPE_DOCUMENT)
|
||||
->setDocument($project);
|
||||
|
||||
|
|
@ -1129,7 +1207,7 @@ App::delete('/v1/projects/:projectId/webhooks/:webhookId')
|
|||
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')
|
||||
|
|
@ -1164,7 +1242,7 @@ App::post('/v1/projects/:projectId/keys')
|
|||
'expire' => $expire,
|
||||
'sdks' => [],
|
||||
'accessedAt' => null,
|
||||
'secret' => \bin2hex(\random_bytes(128)),
|
||||
'secret' => API_KEY_STANDARD . '_' . \bin2hex(\random_bytes(128)),
|
||||
]);
|
||||
|
||||
$key = $dbForConsole->createDocument('keys', $key);
|
||||
|
|
@ -1179,7 +1257,7 @@ App::post('/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')
|
||||
|
|
@ -1211,7 +1289,7 @@ App::get('/v1/projects/:projectId/keys')
|
|||
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')
|
||||
|
|
@ -1245,7 +1323,7 @@ App::get('/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')
|
||||
|
|
@ -1291,7 +1369,7 @@ App::put('/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')
|
||||
|
|
@ -1325,13 +1403,48 @@ App::delete('/v1/projects/:projectId/keys/:keyId')
|
|||
$response->noContent();
|
||||
});
|
||||
|
||||
// JWT Keys
|
||||
|
||||
App::post('/v1/projects/:projectId/jwts')
|
||||
->groups(['api', 'projects'])
|
||||
->desc('Create JWT')
|
||||
->label('scope', 'projects.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'projects')
|
||||
->label('sdk.method', 'createJWT')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_JWT)
|
||||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
->param('scopes', [], new ArrayList(new WhiteList(array_keys(Config::getParam('scopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'List of scopes allowed for JWT key. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed.')
|
||||
->param('duration', 900, new Range(0, 3600), 'Time in seconds before JWT expires. Default duration is 900 seconds, and maximum is 3600 seconds.', true)
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, array $scopes, int $duration, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', $duration, 0);
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
->dynamic(new Document(['jwt' => API_KEY_DYNAMIC . '_' . $jwt->encode([
|
||||
'projectId' => $project->getId(),
|
||||
'scopes' => $scopes
|
||||
])]), Response::MODEL_JWT);
|
||||
});
|
||||
|
||||
// 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')
|
||||
|
|
@ -1339,7 +1452,7 @@ App::post('/v1/projects/:projectId/platforms')
|
|||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_PLATFORM)
|
||||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
->param('type', null, new WhiteList([Origin::CLIENT_TYPE_WEB, Origin::CLIENT_TYPE_FLUTTER_WEB, Origin::CLIENT_TYPE_FLUTTER_IOS, Origin::CLIENT_TYPE_FLUTTER_ANDROID, Origin::CLIENT_TYPE_FLUTTER_LINUX, Origin::CLIENT_TYPE_FLUTTER_MACOS, Origin::CLIENT_TYPE_FLUTTER_WINDOWS, Origin::CLIENT_TYPE_APPLE_IOS, Origin::CLIENT_TYPE_APPLE_MACOS, Origin::CLIENT_TYPE_APPLE_WATCHOS, Origin::CLIENT_TYPE_APPLE_TVOS, Origin::CLIENT_TYPE_ANDROID, Origin::CLIENT_TYPE_UNITY], true), 'Platform type.')
|
||||
->param('type', null, new WhiteList([Origin::CLIENT_TYPE_WEB, Origin::CLIENT_TYPE_FLUTTER_WEB, Origin::CLIENT_TYPE_FLUTTER_IOS, Origin::CLIENT_TYPE_FLUTTER_ANDROID, Origin::CLIENT_TYPE_FLUTTER_LINUX, Origin::CLIENT_TYPE_FLUTTER_MACOS, Origin::CLIENT_TYPE_FLUTTER_WINDOWS, Origin::CLIENT_TYPE_APPLE_IOS, Origin::CLIENT_TYPE_APPLE_MACOS, Origin::CLIENT_TYPE_APPLE_WATCHOS, Origin::CLIENT_TYPE_APPLE_TVOS, Origin::CLIENT_TYPE_ANDROID, Origin::CLIENT_TYPE_UNITY, Origin::CLIENT_TYPE_REACT_NATIVE_IOS, Origin::CLIENT_TYPE_REACT_NATIVE_ANDROID], true), 'Platform type.')
|
||||
->param('name', null, new Text(128), 'Platform name. Max length: 128 chars.')
|
||||
->param('key', '', new Text(256), 'Package name for Android or bundle ID for iOS or macOS. Max length: 256 chars.', true)
|
||||
->param('store', '', new Text(256), 'App store or Google Play store ID. Max length: 256 chars.', true)
|
||||
|
|
@ -1381,7 +1494,7 @@ App::post('/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')
|
||||
|
|
@ -1413,7 +1526,7 @@ App::get('/v1/projects/:projectId/platforms')
|
|||
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')
|
||||
|
|
@ -1447,7 +1560,7 @@ App::get('/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')
|
||||
|
|
@ -1495,7 +1608,7 @@ 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')
|
||||
|
|
@ -1639,7 +1752,7 @@ App::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')
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ use Utopia\Validator\WhiteList;
|
|||
|
||||
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')
|
||||
|
|
@ -149,7 +149,7 @@ App::post('/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')
|
||||
|
|
@ -212,7 +212,7 @@ App::get('/v1/proxy/rules')
|
|||
|
||||
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')
|
||||
|
|
@ -241,7 +241,7 @@ App::get('/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')
|
||||
|
|
@ -277,7 +277,7 @@ App::delete('/v1/proxy/rules/:ruleId')
|
|||
});
|
||||
|
||||
App::patch('/v1/proxy/rules/:ruleId/verification')
|
||||
->desc('Update Rule Verification Status')
|
||||
->desc('Update rule verification status')
|
||||
->groups(['api', 'proxy'])
|
||||
->label('scope', 'rules.write')
|
||||
->label('event', 'rules.[ruleId].update')
|
||||
|
|
|
|||
|
|
@ -889,10 +889,6 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
|
|||
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');
|
||||
|
|
@ -1309,7 +1305,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/push')
|
|||
->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'));
|
||||
$decoder = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 3600, 0);
|
||||
|
||||
try {
|
||||
$decoded = $decoder->decode($jwt);
|
||||
|
|
@ -1320,8 +1316,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/push')
|
|||
if (
|
||||
$decoded['projectId'] !== $project->getId() ||
|
||||
$decoded['bucketId'] !== $bucketId ||
|
||||
$decoded['fileId'] !== $fileId ||
|
||||
$decoded['exp'] < \time()
|
||||
$decoded['fileId'] !== $fileId
|
||||
) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
|
|
@ -1550,7 +1545,7 @@ App::put('/v1/storage/buckets/:bucketId/files/:fileId')
|
|||
});
|
||||
|
||||
App::delete('/v1/storage/buckets/:bucketId/files/:fileId')
|
||||
->desc('Delete File')
|
||||
->desc('Delete file')
|
||||
->groups(['api', 'storage'])
|
||||
->label('scope', 'files.write')
|
||||
->label('event', 'buckets.[bucketId].files.[fileId].delete')
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ use Appwrite\Event\Mail;
|
|||
use Appwrite\Event\Messaging;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Network\Validator\Email;
|
||||
use Appwrite\Platform\Workers\Deletes;
|
||||
use Appwrite\Template\Template;
|
||||
use Appwrite\Utopia\Database\Validator\CustomId;
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Memberships;
|
||||
|
|
@ -42,6 +43,7 @@ use Utopia\Validator\ArrayList;
|
|||
use Utopia\Validator\Assoc;
|
||||
use Utopia\Validator\Host;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
App::post('/v1/teams')
|
||||
->desc('Create team')
|
||||
|
|
@ -338,10 +340,12 @@ App::delete('/v1/teams/:teamId')
|
|||
->label('sdk.response.model', Response::MODEL_NONE)
|
||||
->param('teamId', '', new UID(), 'Team ID.')
|
||||
->inject('response')
|
||||
->inject('getProjectDB')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForDeletes')
|
||||
->action(function (string $teamId, Response $response, Database $dbForProject, Event $queueForEvents, Delete $queueForDeletes) {
|
||||
->inject('queueForEvents')
|
||||
->inject('project')
|
||||
->action(function (string $teamId, Response $response, callable $getProjectDB, Database $dbForProject, Delete $queueForDeletes, Event $queueForEvents, Document $project) {
|
||||
|
||||
$team = $dbForProject->getDocument('teams', $teamId);
|
||||
|
||||
|
|
@ -353,9 +357,14 @@ App::delete('/v1/teams/:teamId')
|
|||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove team from DB');
|
||||
}
|
||||
|
||||
$queueForDeletes
|
||||
->setType(DELETE_TYPE_DOCUMENT)
|
||||
->setDocument($team);
|
||||
$deletes = new Deletes();
|
||||
$deletes->deleteMemberships($getProjectDB, $team, $project);
|
||||
|
||||
if ($project->getId() === 'console') {
|
||||
$queueForDeletes
|
||||
->setType(DELETE_TYPE_TEAM_PROJECTS)
|
||||
->setDocument($team);
|
||||
}
|
||||
|
||||
$queueForEvents
|
||||
->setParam('teamId', $team->getId())
|
||||
|
|
@ -386,7 +395,17 @@ App::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')
|
||||
|
|
@ -860,7 +879,17 @@ App::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')
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
<?php
|
||||
|
||||
use Ahc\Jwt\JWT;
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Auth\MFA\Type;
|
||||
use Appwrite\Auth\MFA\Type\TOTP;
|
||||
|
|
@ -39,6 +40,7 @@ use Utopia\Database\Validator\Query\Limit;
|
|||
use Utopia\Database\Validator\Query\Offset;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Locale\Locale;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\ArrayList;
|
||||
use Utopia\Validator\Assoc;
|
||||
use Utopia\Validator\Boolean;
|
||||
|
|
@ -138,7 +140,7 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e
|
|||
$existingTarget = $dbForProject->findOne('targets', [
|
||||
Query::equal('identifier', [$email]),
|
||||
]);
|
||||
if($existingTarget) {
|
||||
if ($existingTarget) {
|
||||
$user->setAttribute('targets', $existingTarget, Document::SET_TYPE_APPEND);
|
||||
}
|
||||
}
|
||||
|
|
@ -162,7 +164,7 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e
|
|||
$existingTarget = $dbForProject->findOne('targets', [
|
||||
Query::equal('identifier', [$phone]),
|
||||
]);
|
||||
if($existingTarget) {
|
||||
if ($existingTarget) {
|
||||
$user->setAttribute('targets', $existingTarget, Document::SET_TYPE_APPEND);
|
||||
}
|
||||
}
|
||||
|
|
@ -450,7 +452,7 @@ App::post('/v1/users/scrypt-modified')
|
|||
});
|
||||
|
||||
App::post('/v1/users/:userId/targets')
|
||||
->desc('Create User Target')
|
||||
->desc('Create user target')
|
||||
->groups(['api', 'users'])
|
||||
->label('audits.event', 'target.create')
|
||||
->label('audits.resource', 'target/response.$id')
|
||||
|
|
@ -645,7 +647,7 @@ App::get('/v1/users/:userId/prefs')
|
|||
});
|
||||
|
||||
App::get('/v1/users/:userId/targets/:targetId')
|
||||
->desc('Get User Target')
|
||||
->desc('Get user target')
|
||||
->groups(['api', 'users'])
|
||||
->label('scope', 'targets.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_ADMIN])
|
||||
|
|
@ -846,7 +848,7 @@ App::get('/v1/users/:userId/logs')
|
|||
});
|
||||
|
||||
App::get('/v1/users/:userId/targets')
|
||||
->desc('List User Targets')
|
||||
->desc('List user targets')
|
||||
->groups(['api', 'users'])
|
||||
->label('scope', 'targets.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_ADMIN])
|
||||
|
|
@ -901,7 +903,7 @@ App::get('/v1/users/:userId/targets')
|
|||
});
|
||||
|
||||
App::get('/v1/users/identities')
|
||||
->desc('List Identities')
|
||||
->desc('List identities')
|
||||
->groups(['api', 'users'])
|
||||
->label('scope', 'users.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
|
|
@ -1423,7 +1425,7 @@ App::patch('/v1/users/:userId/prefs')
|
|||
});
|
||||
|
||||
App::patch('/v1/users/:userId/targets/:targetId')
|
||||
->desc('Update User target')
|
||||
->desc('Update user target')
|
||||
->groups(['api', 'users'])
|
||||
->label('audits.event', 'target.update')
|
||||
->label('audits.resource', 'target/{response.$id}')
|
||||
|
|
@ -1555,7 +1557,7 @@ App::patch('/v1/users/:userId/mfa')
|
|||
});
|
||||
|
||||
App::get('/v1/users/:userId/mfa/factors')
|
||||
->desc('List Factors')
|
||||
->desc('List factors')
|
||||
->groups(['api', 'users'])
|
||||
->label('scope', 'users.read')
|
||||
->label('usage.metric', 'users.{scope}.requests.read')
|
||||
|
|
@ -1588,7 +1590,7 @@ App::get('/v1/users/:userId/mfa/factors')
|
|||
});
|
||||
|
||||
App::get('/v1/users/:userId/mfa/recovery-codes')
|
||||
->desc('Get MFA Recovery Codes')
|
||||
->desc('Get MFA recovery codes')
|
||||
->groups(['api', 'users'])
|
||||
->label('scope', 'users.read')
|
||||
->label('usage.metric', 'users.{scope}.requests.read')
|
||||
|
|
@ -1623,7 +1625,7 @@ App::get('/v1/users/:userId/mfa/recovery-codes')
|
|||
});
|
||||
|
||||
App::patch('/v1/users/:userId/mfa/recovery-codes')
|
||||
->desc('Create MFA Recovery Codes')
|
||||
->desc('Create MFA recovery codes')
|
||||
->groups(['api', 'users'])
|
||||
->label('event', 'users.[userId].create.mfa.recovery-codes')
|
||||
->label('scope', 'users.write')
|
||||
|
|
@ -1669,7 +1671,7 @@ App::patch('/v1/users/:userId/mfa/recovery-codes')
|
|||
});
|
||||
|
||||
App::put('/v1/users/:userId/mfa/recovery-codes')
|
||||
->desc('Regenerate MFA Recovery Codes')
|
||||
->desc('Regenerate MFA recovery codes')
|
||||
->groups(['api', 'users'])
|
||||
->label('event', 'users.[userId].update.mfa.recovery-codes')
|
||||
->label('scope', 'users.write')
|
||||
|
|
@ -1714,7 +1716,7 @@ App::put('/v1/users/:userId/mfa/recovery-codes')
|
|||
});
|
||||
|
||||
App::delete('/v1/users/:userId/mfa/authenticators/:type')
|
||||
->desc('Delete Authenticator')
|
||||
->desc('Delete authenticator')
|
||||
->groups(['api', 'users'])
|
||||
->label('event', 'users.[userId].delete.mfa')
|
||||
->label('scope', 'users.write')
|
||||
|
|
@ -2095,6 +2097,56 @@ App::delete('/v1/users/identities/:identityId')
|
|||
return $response->noContent();
|
||||
});
|
||||
|
||||
App::post('/v1/users/:userId/jwts')
|
||||
->desc('Create user JWT')
|
||||
->groups(['api', 'users'])
|
||||
->label('scope', 'users.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'users')
|
||||
->label('sdk.method', 'createJWT')
|
||||
->label('sdk.description', '/docs/references/users/create-user-jwt.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_JWT)
|
||||
->param('userId', '', new UID(), 'User ID.')
|
||||
->param('sessionId', '', new UID(), 'Session ID. Use the string \'recent\' to use the most recent session. Defaults to the most recent session.', true)
|
||||
->param('duration', 900, new Range(0, 3600), 'Time in seconds before JWT expires. Default duration is 900 seconds, and maximum is 3600 seconds.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->action(function (string $userId, string $sessionId, int $duration, Response $response, Database $dbForProject) {
|
||||
|
||||
$user = $dbForProject->getDocument('users', $userId);
|
||||
|
||||
if ($user->isEmpty()) {
|
||||
throw new Exception(Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
$sessions = $user->getAttribute('sessions', []);
|
||||
$session = new Document();
|
||||
|
||||
if ($sessionId === 'recent') {
|
||||
// Get most recent
|
||||
$session = \count($sessions) > 0 ? $sessions[\count($sessions) - 1] : new Document();
|
||||
} else {
|
||||
// Find by ID
|
||||
foreach ($sessions as $loopSession) { /** @var Utopia\Database\Document $loopSession */
|
||||
if ($loopSession->getId() == $sessionId) {
|
||||
$session = $loopSession;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', $duration, 0);
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
->dynamic(new Document(['jwt' => $jwt->encode([
|
||||
'userId' => $user->getId(),
|
||||
'sessionId' => $session->isEmpty() ? '' : $session->getId()
|
||||
])]), Response::MODEL_JWT);
|
||||
});
|
||||
|
||||
App::get('/v1/users/usage')
|
||||
->desc('Get users usage stats')
|
||||
->groups(['api', 'users'])
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
|
|||
|
||||
$commentStatus = $isAuthorized ? 'waiting' : 'failed';
|
||||
|
||||
$authorizeUrl = $request->getProtocol() . '://' . $request->getHostname() . "/git/authorize-contributor?projectId={$projectId}&installationId={$installationId}&repositoryId={$repositoryId}&providerPullRequestId={$providerPullRequestId}";
|
||||
$authorizeUrl = $request->getProtocol() . '://' . $request->getHostname() . "/console/git/authorize-contributor?projectId={$projectId}&installationId={$installationId}&repositoryId={$repositoryId}&providerPullRequestId={$providerPullRequestId}";
|
||||
|
||||
$action = $isAuthorized ? ['type' => 'logs'] : ['type' => 'authorize', 'url' => $authorizeUrl];
|
||||
|
||||
|
|
@ -263,7 +263,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
|
|||
};
|
||||
|
||||
App::get('/v1/vcs/github/authorize')
|
||||
->desc('Install GitHub App')
|
||||
->desc('Install GitHub app')
|
||||
->groups(['api', 'vcs'])
|
||||
->label('scope', 'vcs.read')
|
||||
->label('sdk.namespace', 'vcs')
|
||||
|
|
@ -305,7 +305,7 @@ App::get('/v1/vcs/github/authorize')
|
|||
});
|
||||
|
||||
App::get('/v1/vcs/github/callback')
|
||||
->desc('Capture installation and authorization from GitHub App')
|
||||
->desc('Capture installation and authorization from GitHub app')
|
||||
->groups(['api', 'vcs'])
|
||||
->label('scope', 'public')
|
||||
->label('error', __DIR__ . '/../../views/general/error.phtml')
|
||||
|
|
@ -464,6 +464,67 @@ App::get('/v1/vcs/github/callback')
|
|||
->redirect($redirectSuccess);
|
||||
});
|
||||
|
||||
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')
|
||||
->label('sdk.namespace', 'vcs')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.method', 'getRepositoryContents')
|
||||
->label('sdk.description', '')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_VCS_CONTENT_LIST)
|
||||
->param('installationId', '', new Text(256), 'Installation Id')
|
||||
->param('providerRepositoryId', '', new Text(256), 'Repository Id')
|
||||
->param('providerRootDirectory', '', new Text(256, 0), 'Path to get contents of nested directory', true)
|
||||
->inject('gitHub')
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $installationId, string $providerRepositoryId, string $providerRootDirectory, GitHub $github, Response $response, Document $project, Database $dbForConsole) {
|
||||
$installation = $dbForConsole->getDocument('installations', $installationId);
|
||||
|
||||
if ($installation->isEmpty()) {
|
||||
throw new Exception(Exception::INSTALLATION_NOT_FOUND);
|
||||
}
|
||||
|
||||
$providerInstallationId = $installation->getAttribute('providerInstallationId');
|
||||
$privateKey = System::getEnv('_APP_VCS_GITHUB_PRIVATE_KEY');
|
||||
$githubAppId = System::getEnv('_APP_VCS_GITHUB_APP_ID');
|
||||
$github->initializeVariables($providerInstallationId, $privateKey, $githubAppId);
|
||||
|
||||
$owner = $github->getOwnerName($providerInstallationId);
|
||||
try {
|
||||
$repositoryName = $github->getRepositoryName($providerRepositoryId) ?? '';
|
||||
if (empty($repositoryName)) {
|
||||
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
|
||||
}
|
||||
} catch (RepositoryNotFound $e) {
|
||||
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
|
||||
}
|
||||
|
||||
$contents = $github->listRepositoryContents($owner, $repositoryName, $providerRootDirectory);
|
||||
|
||||
$vcsContents = [];
|
||||
foreach ($contents as $content) {
|
||||
$isDirectory = false;
|
||||
if ($content['type'] === GitHub::CONTENTS_DIRECTORY) {
|
||||
$isDirectory = true;
|
||||
}
|
||||
|
||||
$vcsContents[] = new Document([
|
||||
'isDirectory' => $isDirectory,
|
||||
'name' => $content['name'] ?? '',
|
||||
'size' => $content['size'] ?? 0
|
||||
]);
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'contents' => $vcsContents
|
||||
]), Response::MODEL_VCS_CONTENT_LIST);
|
||||
});
|
||||
|
||||
App::post('/v1/vcs/github/installations/:installationId/providerRepositories/:providerRepositoryId/detection')
|
||||
->desc('Detect runtime settings from source code')
|
||||
->groups(['api', 'vcs'])
|
||||
|
|
@ -505,6 +566,7 @@ App::post('/v1/vcs/github/installations/:installationId/providerRepositories/:pr
|
|||
}
|
||||
|
||||
$files = $github->listRepositoryContents($owner, $repositoryName, $providerRootDirectory);
|
||||
$files = \array_column($files, 'name');
|
||||
$languages = $github->listRepositoryLanguages($owner, $repositoryName);
|
||||
|
||||
$detectorFactory = new Detector($files, $languages);
|
||||
|
|
@ -536,7 +598,7 @@ App::post('/v1/vcs/github/installations/:installationId/providerRepositories/:pr
|
|||
});
|
||||
|
||||
App::get('/v1/vcs/github/installations/:installationId/providerRepositories')
|
||||
->desc('List Repositories')
|
||||
->desc('List repositories')
|
||||
->groups(['api', 'vcs'])
|
||||
->label('scope', 'vcs.read')
|
||||
->label('sdk.namespace', 'vcs')
|
||||
|
|
@ -586,6 +648,7 @@ App::get('/v1/vcs/github/installations/:installationId/providerRepositories')
|
|||
return function () use ($repo, $github) {
|
||||
try {
|
||||
$files = $github->listRepositoryContents($repo['organization'], $repo['name'], '');
|
||||
$files = \array_column($files, 'name');
|
||||
$languages = $github->listRepositoryLanguages($repo['organization'], $repo['name']);
|
||||
|
||||
$detectorFactory = new Detector($files, $languages);
|
||||
|
|
@ -780,7 +843,7 @@ App::get('/v1/vcs/github/installations/:installationId/providerRepositories/:pro
|
|||
});
|
||||
|
||||
App::get('/v1/vcs/github/installations/:installationId/providerRepositories/:providerRepositoryId/branches')
|
||||
->desc('List Repository Branches')
|
||||
->desc('List repository branches')
|
||||
->groups(['api', 'vcs'])
|
||||
->label('scope', 'vcs.read')
|
||||
->label('sdk.namespace', 'vcs')
|
||||
|
|
@ -829,7 +892,7 @@ App::get('/v1/vcs/github/installations/:installationId/providerRepositories/:pro
|
|||
});
|
||||
|
||||
App::post('/v1/vcs/github/events')
|
||||
->desc('Create Event')
|
||||
->desc('Create event')
|
||||
->groups(['api', 'vcs'])
|
||||
->label('scope', 'public')
|
||||
->inject('gitHub')
|
||||
|
|
@ -1057,7 +1120,7 @@ App::get('/v1/vcs/installations/:installationId')
|
|||
});
|
||||
|
||||
App::delete('/v1/vcs/installations/:installationId')
|
||||
->desc('Delete Installation')
|
||||
->desc('Delete installation')
|
||||
->groups(['api', 'vcs'])
|
||||
->label('scope', 'vcs.write')
|
||||
->label('sdk.namespace', 'vcs')
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
require_once __DIR__ . '/../init.php';
|
||||
|
||||
use Ahc\Jwt\JWT;
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Event\Certificate;
|
||||
use Appwrite\Event\Event;
|
||||
|
|
@ -11,9 +12,11 @@ use Appwrite\Network\Validator\Origin;
|
|||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Request\Filters\V16 as RequestV16;
|
||||
use Appwrite\Utopia\Request\Filters\V17 as RequestV17;
|
||||
use Appwrite\Utopia\Request\Filters\V18 as RequestV18;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Filters\V16 as ResponseV16;
|
||||
use Appwrite\Utopia\Response\Filters\V17 as ResponseV17;
|
||||
use Appwrite\Utopia\Response\Filters\V18 as ResponseV18;
|
||||
use Appwrite\Utopia\View;
|
||||
use Executor\Executor;
|
||||
use MaxMind\Db\Reader;
|
||||
|
|
@ -22,6 +25,7 @@ use Utopia\App;
|
|||
use Utopia\CLI\Console;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Query;
|
||||
|
|
@ -29,6 +33,7 @@ use Utopia\Database\Validator\Authorization;
|
|||
use Utopia\Domains\Domain;
|
||||
use Utopia\DSN\DSN;
|
||||
use Utopia\Locale\Locale;
|
||||
use Utopia\Logger\Adapter\Sentry;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\Logger\Log\User;
|
||||
use Utopia\Logger\Logger;
|
||||
|
|
@ -93,6 +98,9 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo
|
|||
$type = $route->getAttribute('resourceType');
|
||||
|
||||
if ($type === 'function') {
|
||||
$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') {
|
||||
if ($request->getMethod() !== Request::METHOD_GET) {
|
||||
|
|
@ -130,6 +138,7 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo
|
|||
|
||||
$version = $function->getAttribute('version', 'v2');
|
||||
$runtimes = Config::getParam($version === 'v2' ? 'runtimes-v2' : 'runtimes', []);
|
||||
$spec = Config::getParam('runtime-specifications')[$function->getAttribute('specification', APP_FUNCTION_SPECIFICATION_DEFAULT)];
|
||||
|
||||
$runtime = (isset($runtimes[$function->getAttribute('runtime', '')])) ? $runtimes[$function->getAttribute('runtime', '')] : null;
|
||||
|
||||
|
|
@ -163,7 +172,15 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo
|
|||
throw new AppwriteException(AppwriteException::USER_UNAUTHORIZED, 'To execute function using domain, execute permissions must include "any" or "guests"');
|
||||
}
|
||||
|
||||
$jwtExpiry = $function->getAttribute('timeout', 900);
|
||||
$jwtObj = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', $jwtExpiry, 0);
|
||||
$apiKey = $jwtObj->encode([
|
||||
'projectId' => $project->getId(),
|
||||
'scopes' => $function->getAttribute('scopes', [])
|
||||
]);
|
||||
|
||||
$headers = \array_merge([], $requestHeaders);
|
||||
$headers['x-appwrite-key'] = API_KEY_DYNAMIC . '_' . $apiKey;
|
||||
$headers['x-appwrite-trigger'] = 'http';
|
||||
$headers['x-appwrite-user-id'] = '';
|
||||
$headers['x-appwrite-user-jwt'] = '';
|
||||
|
|
@ -242,14 +259,36 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo
|
|||
$vars[$var->getAttribute('key')] = $var->getAttribute('value', '');
|
||||
}
|
||||
|
||||
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https';
|
||||
$hostname = System::getEnv('_APP_DOMAIN');
|
||||
$endpoint = $protocol . '://' . $hostname . "/v1";
|
||||
|
||||
// Appwrite vars
|
||||
$vars = \array_merge($vars, [
|
||||
'APPWRITE_FUNCTION_API_ENDPOINT' => $endpoint,
|
||||
'APPWRITE_FUNCTION_ID' => $functionId,
|
||||
'APPWRITE_FUNCTION_NAME' => $function->getAttribute('name'),
|
||||
'APPWRITE_FUNCTION_DEPLOYMENT' => $deployment->getId(),
|
||||
'APPWRITE_FUNCTION_PROJECT_ID' => $project->getId(),
|
||||
'APPWRITE_FUNCTION_RUNTIME_NAME' => $runtime['name'] ?? '',
|
||||
'APPWRITE_FUNCTION_RUNTIME_VERSION' => $runtime['version'] ?? '',
|
||||
'APPWRITE_FUNCTION_CPUS' => $spec['cpus'] ?? APP_FUNCTION_CPUS_DEFAULT,
|
||||
'APPWRITE_FUNCTION_MEMORY' => $spec['memory'] ?? APP_FUNCTION_MEMORY_DEFAULT,
|
||||
'APPWRITE_VERSION' => APP_VERSION_STABLE,
|
||||
'APPWRITE_REGION' => $project->getAttribute('region'),
|
||||
'APPWRITE_DEPLOYMENT_TYPE' => $deployment->getAttribute('type', ''),
|
||||
'APPWRITE_VCS_REPOSITORY_ID' => $deployment->getAttribute('providerRepositoryId', ''),
|
||||
'APPWRITE_VCS_REPOSITORY_NAME' => $deployment->getAttribute('providerRepositoryName', ''),
|
||||
'APPWRITE_VCS_REPOSITORY_OWNER' => $deployment->getAttribute('providerRepositoryOwner', ''),
|
||||
'APPWRITE_VCS_REPOSITORY_URL' => $deployment->getAttribute('providerRepositoryUrl', ''),
|
||||
'APPWRITE_VCS_REPOSITORY_BRANCH' => $deployment->getAttribute('providerBranch', ''),
|
||||
'APPWRITE_VCS_REPOSITORY_BRANCH_URL' => $deployment->getAttribute('providerBranchUrl', ''),
|
||||
'APPWRITE_VCS_COMMIT_HASH' => $deployment->getAttribute('providerCommitHash', ''),
|
||||
'APPWRITE_VCS_COMMIT_MESSAGE' => $deployment->getAttribute('providerCommitMessage', ''),
|
||||
'APPWRITE_VCS_COMMIT_URL' => $deployment->getAttribute('providerCommitUrl', ''),
|
||||
'APPWRITE_VCS_COMMIT_AUTHOR_NAME' => $deployment->getAttribute('providerCommitAuthor', ''),
|
||||
'APPWRITE_VCS_COMMIT_AUTHOR_URL' => $deployment->getAttribute('providerCommitAuthorUrl', ''),
|
||||
'APPWRITE_VCS_ROOT_DIRECTORY' => $deployment->getAttribute('providerRootDirectory', ''),
|
||||
]);
|
||||
|
||||
/** Execute function */
|
||||
|
|
@ -272,6 +311,9 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo
|
|||
method: $method,
|
||||
headers: $headers,
|
||||
runtimeEntrypoint: $command,
|
||||
cpus: $spec['cpus'] ?? APP_FUNCTION_CPUS_DEFAULT,
|
||||
memory: $spec['memory'] ?? APP_FUNCTION_MEMORY_DEFAULT,
|
||||
logging: $function->getAttribute('logging', true),
|
||||
requestTimeout: 30
|
||||
);
|
||||
|
||||
|
|
@ -283,7 +325,7 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo
|
|||
}
|
||||
|
||||
/** Update execution status */
|
||||
$status = $executionResponse['statusCode'] >= 400 ? 'failed' : 'completed';
|
||||
$status = $executionResponse['statusCode'] >= 500 ? 'failed' : 'completed';
|
||||
$execution->setAttribute('status', $status);
|
||||
$execution->setAttribute('responseStatusCode', $executionResponse['statusCode']);
|
||||
$execution->setAttribute('responseHeaders', $headersFiltered);
|
||||
|
|
@ -305,21 +347,27 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo
|
|||
throw $th;
|
||||
}
|
||||
} finally {
|
||||
$fileSize = 0;
|
||||
$file = $request->getFiles('file');
|
||||
if (!empty($file)) {
|
||||
$fileSize = (\is_array($file['size']) && isset($file['size'][0])) ? $file['size'][0] : $file['size'];
|
||||
}
|
||||
|
||||
$queueForUsage
|
||||
->addMetric(METRIC_NETWORK_REQUESTS, 1)
|
||||
->addMetric(METRIC_NETWORK_INBOUND, $request->getSize() + $fileSize)
|
||||
->addMetric(METRIC_NETWORK_OUTBOUND, $response->getSize())
|
||||
->addMetric(METRIC_EXECUTIONS, 1)
|
||||
->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS), 1)
|
||||
->addMetric(METRIC_EXECUTIONS_COMPUTE, (int)($execution->getAttribute('duration') * 1000)) // per project
|
||||
->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS_COMPUTE), (int)($execution->getAttribute('duration') * 1000)) // per function
|
||||
->addMetric(METRIC_EXECUTIONS_MB_SECONDS, (int)(512 * $execution->getAttribute('duration', 0)))
|
||||
->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS_MB_SECONDS), (int)(512 * $execution->getAttribute('duration', 0)))
|
||||
->addMetric(METRIC_EXECUTIONS_MB_SECONDS, (int)(($spec['memory'] ?? APP_FUNCTION_MEMORY_DEFAULT) * $execution->getAttribute('duration', 0) * ($spec['cpus'] ?? APP_FUNCTION_CPUS_DEFAULT)))
|
||||
->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)))
|
||||
->setProject($project)
|
||||
->trigger()
|
||||
;
|
||||
|
||||
if ($function->getAttribute('logging')) {
|
||||
/** @var Document $execution */
|
||||
$execution = Authorization::skip(fn () => $dbForProject->createDocument('executions', $execution));
|
||||
}
|
||||
$execution = Authorization::skip(fn () => $dbForProject->createDocument('executions', $execution));
|
||||
}
|
||||
|
||||
$execution->setAttribute('logs', '');
|
||||
|
|
@ -335,13 +383,6 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo
|
|||
|
||||
$body = $execution['responseBody'] ?? '';
|
||||
|
||||
$encodingKey = \array_search('x-open-runtimes-encoding', \array_column($execution['responseHeaders'], 'name'));
|
||||
if ($encodingKey !== false) {
|
||||
if (($execution['responseHeaders'][$encodingKey]['value'] ?? '') === 'base64') {
|
||||
$body = \base64_decode($body);
|
||||
}
|
||||
}
|
||||
|
||||
$contentType = 'text/plain';
|
||||
foreach ($execution['responseHeaders'] as $header) {
|
||||
if (\strtolower($header['name']) === 'content-type') {
|
||||
|
|
@ -443,6 +484,9 @@ App::init()
|
|||
if (version_compare($requestFormat, '1.5.0', '<')) {
|
||||
$request->addFilter(new RequestV17());
|
||||
}
|
||||
if (version_compare($requestFormat, '1.6.0', '<')) {
|
||||
$request->addFilter(new RequestV18());
|
||||
}
|
||||
}
|
||||
|
||||
$domain = $request->getHostname();
|
||||
|
|
@ -559,6 +603,9 @@ App::init()
|
|||
if (version_compare($responseFormat, '1.5.0', '<')) {
|
||||
$response->addFilter(new ResponseV17());
|
||||
}
|
||||
if (version_compare($responseFormat, '1.6.0', '<')) {
|
||||
$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");
|
||||
}
|
||||
|
|
@ -731,16 +778,24 @@ App::error()
|
|||
$providerName = System::getEnv('_APP_EXPERIMENT_LOGGING_PROVIDER', '');
|
||||
$providerConfig = System::getEnv('_APP_EXPERIMENT_LOGGING_CONFIG', '');
|
||||
|
||||
if (!(empty($providerName) || empty($providerConfig))) {
|
||||
if (!Logger::hasProvider($providerName)) {
|
||||
throw new Exception("Logging provider not supported. Logging is disabled");
|
||||
}
|
||||
try {
|
||||
$loggingProvider = new DSN($providerConfig ?? '');
|
||||
$providerName = $loggingProvider->getScheme();
|
||||
|
||||
$classname = '\\Utopia\\Logger\\Adapter\\' . \ucfirst($providerName);
|
||||
$adapter = new $classname($providerConfig);
|
||||
$logger = new Logger($adapter);
|
||||
$logger->setSample(0.04);
|
||||
$publish = true;
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -809,8 +864,12 @@ App::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 */
|
||||
|
|
@ -904,7 +963,7 @@ App::get('/robots.txt')
|
|||
$host = $request->getHostname() ?? '';
|
||||
$mainDomain = System::getEnv('_APP_DOMAIN', '');
|
||||
|
||||
if ($host === $mainDomain) {
|
||||
if ($host === $mainDomain || $host === 'localhost') {
|
||||
$template = new View(__DIR__ . '/../views/general/robots.phtml');
|
||||
$response->text($template->render(false));
|
||||
} else {
|
||||
|
|
@ -929,7 +988,7 @@ App::get('/humans.txt')
|
|||
$host = $request->getHostname() ?? '';
|
||||
$mainDomain = System::getEnv('_APP_DOMAIN', '');
|
||||
|
||||
if ($host === $mainDomain) {
|
||||
if ($host === $mainDomain || $host === 'localhost') {
|
||||
$template = new View(__DIR__ . '/../views/general/humans.phtml');
|
||||
$response->text($template->render(false));
|
||||
} else {
|
||||
|
|
@ -990,6 +1049,38 @@ App::get('/.well-known/acme-challenge/*')
|
|||
include_once __DIR__ . '/shared/api.php';
|
||||
include_once __DIR__ . '/shared/api/auth.php';
|
||||
|
||||
App::get('/v1/ping')
|
||||
->groups(['api', 'general'])
|
||||
->desc('Test the connection between the Appwrite and the SDK.')
|
||||
->label('scope', 'global')
|
||||
->label('event', 'projects.[projectId].ping')
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForConsole')
|
||||
->inject('queueForEvents')
|
||||
->action(function (Response $response, Document $project, Database $dbForConsole, Event $queueForEvents) {
|
||||
if ($project->isEmpty()) {
|
||||
throw new AppwriteException(AppwriteException::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$pingCount = $project->getAttribute('pingCount', 0) + 1;
|
||||
$pingedAt = DateTime::now();
|
||||
|
||||
$project
|
||||
->setAttribute('pingCount', $pingCount)
|
||||
->setAttribute('pingedAt', $pingedAt);
|
||||
|
||||
Authorization::skip(function () use ($dbForConsole, $project) {
|
||||
$dbForConsole->updateDocument('projects', $project->getId(), $project);
|
||||
});
|
||||
|
||||
$queueForEvents
|
||||
->setParam('projectId', $project->getId())
|
||||
->setPayload($response->output($project, Response::MODEL_PROJECT));
|
||||
|
||||
$response->text('Pong!');
|
||||
});
|
||||
|
||||
App::wildcard()
|
||||
->groups(['api'])
|
||||
->label('scope', 'global')
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ 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;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
|
|
@ -154,6 +155,55 @@ App::patch('/v1/mock/functions-v2')
|
|||
$response->noContent();
|
||||
});
|
||||
|
||||
App::post('/v1/mock/api-key-unprefixed')
|
||||
->desc('Create API Key (without standard prefix)')
|
||||
->groups(['mock', 'api', 'projects'])
|
||||
->label('scope', 'public')
|
||||
->label('docs', false)
|
||||
->param('projectId', '', new UID(), 'Project ID.')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, Response $response, Database $dbForConsole) {
|
||||
$isDevelopment = System::getEnv('_APP_ENV', 'development') === 'development';
|
||||
|
||||
if (!$isDevelopment) {
|
||||
throw new Exception(Exception::GENERAL_NOT_IMPLEMENTED);
|
||||
}
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$scopes = array_keys(Config::getParam('scopes'));
|
||||
|
||||
$key = new Document([
|
||||
'$id' => ID::unique(),
|
||||
'$permissions' => [
|
||||
Permission::read(Role::any()),
|
||||
Permission::update(Role::any()),
|
||||
Permission::delete(Role::any()),
|
||||
],
|
||||
'projectInternalId' => $project->getInternalId(),
|
||||
'projectId' => $project->getId(),
|
||||
'name' => 'Outdated key',
|
||||
'scopes' => $scopes,
|
||||
'expire' => null,
|
||||
'sdks' => [],
|
||||
'accessedAt' => null,
|
||||
'secret' => \bin2hex(\random_bytes(128)),
|
||||
]);
|
||||
|
||||
$key = $dbForConsole->createDocument('keys', $key);
|
||||
|
||||
$dbForConsole->purgeCachedDocument('projects', $project->getId());
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
->dynamic($key, Response::MODEL_KEY);
|
||||
});
|
||||
|
||||
App::get('/v1/mock/github/callback')
|
||||
->desc('Create installation document using GitHub installation id')
|
||||
->groups(['mock', 'api', 'vcs'])
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
<?php
|
||||
|
||||
use Ahc\Jwt\JWT;
|
||||
use Ahc\Jwt\JWTException;
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Auth\MFA\Type\TOTP;
|
||||
use Appwrite\Event\Audit;
|
||||
|
|
@ -93,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) {
|
||||
|
|
@ -158,103 +160,152 @@ App::init()
|
|||
->inject('session')
|
||||
->inject('servers')
|
||||
->inject('mode')
|
||||
->action(function (App $utopia, Request $request, Database $dbForConsole, Document $project, Document $user, ?Document $session, array $servers, string $mode) {
|
||||
->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');
|
||||
/** Allowed Scopes for the role */
|
||||
$scopes = $roles[$role]['scopes'];
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
$apiKey = $request->getHeader('x-appwrite-key', '');
|
||||
|
||||
$roles = Config::getParam('roles', []);
|
||||
$scope = $route->getLabel('scope', 'none'); // Allowed scope for chosen route
|
||||
$scopes = $roles[$role]['scopes']; // Allowed scopes for user role
|
||||
|
||||
$authKey = $request->getHeader('x-appwrite-key', '');
|
||||
|
||||
if (!empty($authKey)) { // API Key authentication
|
||||
// API Key authentication
|
||||
if (!empty($apiKey)) {
|
||||
// Do not allow API key and session to be set at the same time
|
||||
if (!$user->isEmpty()) {
|
||||
throw new Exception(Exception::USER_API_KEY_AND_SESSION_SET);
|
||||
}
|
||||
|
||||
// Check if given key match project API keys
|
||||
$key = $project->find('secret', $authKey, 'keys');
|
||||
if ($key) {
|
||||
$user = new Document([
|
||||
'$id' => '',
|
||||
'status' => true,
|
||||
'email' => 'app.' . $project->getId() . '@service.' . $request->getHostname(),
|
||||
'password' => '',
|
||||
'name' => $project->getAttribute('name', 'Untitled'),
|
||||
]);
|
||||
// Remove after migration
|
||||
if (!\str_contains($apiKey, '_')) {
|
||||
$keyType = API_KEY_STANDARD;
|
||||
$authKey = $apiKey;
|
||||
} else {
|
||||
[ $keyType, $authKey ] = \explode('_', $apiKey, 2);
|
||||
}
|
||||
|
||||
$role = Auth::USER_ROLE_APPS;
|
||||
$scopes = \array_merge($roles[$role]['scopes'], $key->getAttribute('scopes', []));
|
||||
if ($keyType === API_KEY_DYNAMIC) {
|
||||
// Dynamic key
|
||||
|
||||
$expire = $key->getAttribute('expire');
|
||||
if (!empty($expire) && $expire < DateTime::formatTz(DateTime::now())) {
|
||||
throw new Exception(Exception::PROJECT_KEY_EXPIRED);
|
||||
$jwtObj = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 3600, 0);
|
||||
|
||||
try {
|
||||
$payload = $jwtObj->decode($authKey);
|
||||
} catch (JWTException $error) {
|
||||
throw new Exception(Exception::API_KEY_EXPIRED);
|
||||
}
|
||||
|
||||
Authorization::setRole(Auth::USER_ROLE_APPS);
|
||||
Authorization::setDefaultStatus(false); // Cancel security segmentation for API keys.
|
||||
$projectId = $payload['projectId'] ?? '';
|
||||
$tokenScopes = $payload['scopes'] ?? [];
|
||||
|
||||
$accessedAt = $key->getAttribute('accessedAt', '');
|
||||
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_KEY_ACCCESS)) > $accessedAt) {
|
||||
$key->setAttribute('accessedAt', DateTime::now());
|
||||
$dbForConsole->updateDocument('keys', $key->getId(), $key);
|
||||
$dbForConsole->purgeCachedDocument('projects', $project->getId());
|
||||
// JWT includes project ID for better security
|
||||
if ($projectId === $project->getId()) {
|
||||
$user = new Document([
|
||||
'$id' => '',
|
||||
'status' => true,
|
||||
'email' => 'app.' . $project->getId() . '@service.' . $request->getHostname(),
|
||||
'password' => '',
|
||||
'name' => $project->getAttribute('name', 'Untitled'),
|
||||
]);
|
||||
|
||||
$role = Auth::USER_ROLE_APPS;
|
||||
$scopes = \array_merge($roles[$role]['scopes'], $tokenScopes);
|
||||
|
||||
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.
|
||||
// Regular key
|
||||
|
||||
$sdkValidator = new WhiteList($servers, true);
|
||||
$sdk = $request->getHeader('x-sdk-name', 'UNKNOWN');
|
||||
if ($sdkValidator->isValid($sdk)) {
|
||||
$sdks = $key->getAttribute('sdks', []);
|
||||
if (!in_array($sdk, $sdks)) {
|
||||
array_push($sdks, $sdk);
|
||||
$key->setAttribute('sdks', $sdks);
|
||||
// Check if given key match project API keys
|
||||
$key = $project->find('secret', $apiKey, 'keys');
|
||||
if ($key) {
|
||||
$user = new Document([
|
||||
'$id' => '',
|
||||
'status' => true,
|
||||
'email' => 'app.' . $project->getId() . '@service.' . $request->getHostname(),
|
||||
'password' => '',
|
||||
'name' => $project->getAttribute('name', 'Untitled'),
|
||||
]);
|
||||
|
||||
/** Update access time as well */
|
||||
$key->setAttribute('accessedAt', Datetime::now());
|
||||
$role = Auth::USER_ROLE_APPS;
|
||||
$scopes = \array_merge($roles[$role]['scopes'], $key->getAttribute('scopes', []));
|
||||
|
||||
$expire = $key->getAttribute('expire');
|
||||
if (!empty($expire) && $expire < DateTime::formatTz(DateTime::now())) {
|
||||
throw new Exception(Exception::PROJECT_KEY_EXPIRED);
|
||||
}
|
||||
|
||||
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) {
|
||||
$key->setAttribute('accessedAt', DateTime::now());
|
||||
$dbForConsole->updateDocument('keys', $key->getId(), $key);
|
||||
$dbForConsole->purgeCachedDocument('projects', $project->getId());
|
||||
}
|
||||
|
||||
$sdkValidator = new WhiteList($servers, true);
|
||||
$sdk = $request->getHeader('x-sdk-name', 'UNKNOWN');
|
||||
if ($sdkValidator->isValid($sdk)) {
|
||||
$sdks = $key->getAttribute('sdks', []);
|
||||
if (!in_array($sdk, $sdks)) {
|
||||
array_push($sdks, $sdk);
|
||||
$key->setAttribute('sdks', $sdks);
|
||||
|
||||
/** Update access time as well */
|
||||
$key->setAttribute('accessedAt', Datetime::now());
|
||||
$dbForConsole->updateDocument('keys', $key->getId(), $key);
|
||||
$dbForConsole->purgeCachedDocument('projects', $project->getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($adminRoles)) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
$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 (
|
||||
|
|
@ -265,14 +316,14 @@ App::init()
|
|||
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);
|
||||
}
|
||||
|
|
@ -556,10 +607,11 @@ App::shutdown()
|
|||
/**
|
||||
* Trigger functions.
|
||||
*/
|
||||
$queueForFunctions
|
||||
->from($queueForEvents)
|
||||
->trigger();
|
||||
|
||||
if (!$queueForEvents->isPaused()) {
|
||||
$queueForFunctions
|
||||
->from($queueForEvents)
|
||||
->trigger();
|
||||
}
|
||||
/**
|
||||
* Trigger webhooks.
|
||||
*/
|
||||
|
|
@ -720,12 +772,23 @@ App::shutdown()
|
|||
->trigger();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update project last activity
|
||||
*/
|
||||
if (!$project->isEmpty() && $project->getId() !== 'console') {
|
||||
$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));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update user last activity
|
||||
*/
|
||||
if (!$user->isEmpty()) {
|
||||
$accessedAt = $user->getAttribute('accessedAt', '');
|
||||
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_USER_ACCCESS)) > $accessedAt) {
|
||||
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_USER_ACCESS)) > $accessedAt) {
|
||||
$user->setAttribute('accessedAt', DateTime::now());
|
||||
|
||||
if (APP_MODE_ADMIN !== $mode) {
|
||||
|
|
|
|||
|
|
@ -16,8 +16,7 @@ App::init()
|
|||
;
|
||||
});
|
||||
|
||||
App::get('/console/*')
|
||||
->alias('/')
|
||||
App::get('/')
|
||||
->alias('auth/*')
|
||||
->alias('/invite')
|
||||
->alias('/login')
|
||||
|
|
@ -31,45 +30,14 @@ App::get('/console/*')
|
|||
->inject('request')
|
||||
->inject('response')
|
||||
->action(function (Request $request, Response $response) {
|
||||
$fallback = file_get_contents(__DIR__ . '/../../../console/index.html');
|
||||
|
||||
// Card SSR
|
||||
if (\str_starts_with($request->getURI(), '/card')) {
|
||||
$urlCunks = \explode('/', $request->getURI());
|
||||
$userId = $urlCunks[\count($urlCunks) - 1] ?? '';
|
||||
|
||||
$domain = $request->getProtocol() . '://' . $request->getHostname();
|
||||
|
||||
if (!empty($userId)) {
|
||||
$ogImageUrl = $domain . '/v1/cards/cloud-og?userId=' . $userId;
|
||||
} else {
|
||||
$ogImageUrl = $domain . '/v1/cards/cloud-og?mock=normal';
|
||||
}
|
||||
|
||||
$ogTags = [
|
||||
'<title>Appwrite Cloud Card</title>',
|
||||
'<meta name="description" content="Appwrite Cloud is now LIVE! Share your Cloud card for a chance to win an exclusive Cloud hoodie!">',
|
||||
'<meta property="og:url" content="' . $domain . $request->getURI() . '">',
|
||||
'<meta name="og:image:type" content="image/png">',
|
||||
'<meta name="og:image:width" content="1008">',
|
||||
'<meta name="og:image:height" content="1008">',
|
||||
'<meta property="og:type" content="website">',
|
||||
'<meta property="og:title" content="Appwrite Cloud Card">',
|
||||
'<meta property="og:description" content="Appwrite Cloud is now LIVE! Share your Cloud card for a chance to win an exclusive Cloud hoodie!">',
|
||||
'<meta property="og:image" content="' . $ogImageUrl . '">',
|
||||
'<meta name="twitter:card" content="summary_large_image">',
|
||||
'<meta property="twitter:domain" content="' . $request->getHostname() . '">',
|
||||
'<meta property="twitter:url" content="' . $domain . $request->getURI() . '">',
|
||||
'<meta name="twitter:title" content="Appwrite Cloud Card">',
|
||||
'<meta name="twitter:image:type" content="image/png">',
|
||||
'<meta name="twitter:image:width" content="1008">',
|
||||
'<meta name="twitter:image:height" content="1008">',
|
||||
'<meta name="twitter:description" content="Appwrite Cloud is now LIVE! Share your Cloud card for a chance to win an exclusive Cloud hoodie!">',
|
||||
'<meta name="twitter:image" content="' . $ogImageUrl . '">',
|
||||
];
|
||||
|
||||
$fallback = \str_replace('<!-- {{CLOUD_OG}} -->', \implode('', $ogTags), $fallback);
|
||||
$url = parse_url($request->getURI());
|
||||
$target = "/console{$url['path']}";
|
||||
$params = $request->getParams();
|
||||
if (!empty($params)) {
|
||||
$target .= "?" . \http_build_query($params);
|
||||
}
|
||||
|
||||
$response->html($fallback);
|
||||
if ($url['fragment'] ?? false) {
|
||||
$target .= "#{$url['fragment']}";
|
||||
}
|
||||
$response->redirect($target);
|
||||
});
|
||||
|
|
|
|||
10
app/http.php
10
app/http.php
|
|
@ -58,8 +58,6 @@ $http->on(Constant::EVENT_AFTER_RELOAD, function ($server, $workerId) {
|
|||
Console::success('Reload completed...');
|
||||
});
|
||||
|
||||
Files::load(__DIR__ . '/../console');
|
||||
|
||||
include __DIR__ . '/controllers/general.php';
|
||||
|
||||
$http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $register) {
|
||||
|
|
@ -301,8 +299,12 @@ $http->on(Constant::EVENT_REQUEST, function (SwooleRequest $swooleRequest, Swool
|
|||
$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());
|
||||
}
|
||||
}
|
||||
|
||||
Console::error('[Error] Type: ' . get_class($th));
|
||||
|
|
|
|||
172
app/init.php
172
app/init.php
|
|
@ -33,6 +33,7 @@ use Appwrite\Event\Messaging;
|
|||
use Appwrite\Event\Migration;
|
||||
use Appwrite\Event\Usage;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Functions\Specification;
|
||||
use Appwrite\GraphQL\Promises\Adapter\Swoole;
|
||||
use Appwrite\GraphQL\Schema;
|
||||
use Appwrite\Hooks\Hooks;
|
||||
|
|
@ -40,6 +41,7 @@ use Appwrite\Network\Validator\Email;
|
|||
use Appwrite\Network\Validator\Origin;
|
||||
use Appwrite\OpenSSL\OpenSSL;
|
||||
use Appwrite\URL\URL as AppwriteURL;
|
||||
use Appwrite\Utopia\Request;
|
||||
use MaxMind\Db\Reader;
|
||||
use PHPMailer\PHPMailer\PHPMailer;
|
||||
use Swoole\Database\PDOProxy;
|
||||
|
|
@ -62,6 +64,10 @@ use Utopia\Database\Validator\Structure;
|
|||
use Utopia\Domains\Validator\PublicDomain;
|
||||
use Utopia\DSN\DSN;
|
||||
use Utopia\Locale\Locale;
|
||||
use Utopia\Logger\Adapter\AppSignal;
|
||||
use Utopia\Logger\Adapter\LogOwl;
|
||||
use Utopia\Logger\Adapter\Raygun;
|
||||
use Utopia\Logger\Adapter\Sentry;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\Logger\Logger;
|
||||
use Utopia\Pools\Group;
|
||||
|
|
@ -109,11 +115,12 @@ const APP_LIMIT_SUBSCRIBERS_SUBQUERY = 1_000_000;
|
|||
const APP_LIMIT_WRITE_RATE_DEFAULT = 60; // Default maximum write rate per rate period
|
||||
const APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT = 60; // Default maximum write rate period in seconds
|
||||
const APP_LIMIT_LIST_DEFAULT = 25; // Default maximum number of items to return in list API calls
|
||||
const APP_KEY_ACCCESS = 24 * 60 * 60; // 24 hours
|
||||
const APP_USER_ACCCESS = 24 * 60 * 60; // 24 hours
|
||||
const APP_KEY_ACCESS = 24 * 60 * 60; // 24 hours
|
||||
const APP_USER_ACCESS = 24 * 60 * 60; // 24 hours
|
||||
const APP_PROJECT_ACCESS = 24 * 60 * 60; // 24 hours
|
||||
const APP_CACHE_UPDATE = 24 * 60 * 60; // 24 hours
|
||||
const APP_CACHE_BUSTER = 4330;
|
||||
const APP_VERSION_STABLE = '1.5.10';
|
||||
const APP_CACHE_BUSTER = 4318;
|
||||
const APP_VERSION_STABLE = '1.6.0';
|
||||
const APP_DATABASE_ATTRIBUTE_EMAIL = 'email';
|
||||
const APP_DATABASE_ATTRIBUTE_ENUM = 'enum';
|
||||
const APP_DATABASE_ATTRIBUTE_IP = 'ip';
|
||||
|
|
@ -142,6 +149,12 @@ const APP_SOCIAL_DEV = 'https://dev.to/appwrite';
|
|||
const APP_SOCIAL_STACKSHARE = 'https://stackshare.io/appwrite';
|
||||
const APP_SOCIAL_YOUTUBE = 'https://www.youtube.com/c/appwrite?sub_confirmation=1';
|
||||
const APP_HOSTNAME_INTERNAL = 'appwrite';
|
||||
const APP_FUNCTION_SPECIFICATION_DEFAULT = Specification::S_05VCPU_512MB;
|
||||
const APP_FUNCTION_CPUS_DEFAULT = 0.5;
|
||||
const APP_FUNCTION_MEMORY_DEFAULT = 512;
|
||||
const APP_PLATFORM_SERVER = 'server';
|
||||
const APP_PLATFORM_CLIENT = 'client';
|
||||
const APP_PLATFORM_CONSOLE = 'console';
|
||||
|
||||
// Database Reconnect
|
||||
const DATABASE_RECONNECT_SLEEP = 2;
|
||||
|
|
@ -167,7 +180,7 @@ const DELETE_TYPE_PROJECTS = 'projects';
|
|||
const DELETE_TYPE_FUNCTIONS = 'functions';
|
||||
const DELETE_TYPE_DEPLOYMENTS = 'deployments';
|
||||
const DELETE_TYPE_USERS = 'users';
|
||||
const DELETE_TYPE_TEAMS = 'teams';
|
||||
const DELETE_TYPE_TEAM_PROJECTS = 'teams_projects';
|
||||
const DELETE_TYPE_EXECUTIONS = 'executions';
|
||||
const DELETE_TYPE_AUDIT = 'audit';
|
||||
const DELETE_TYPE_ABUSE = 'abuse';
|
||||
|
|
@ -208,18 +221,34 @@ const FUNCTION_ALLOWLIST_HEADERS_RESPONSE = ['content-type', 'content-length'];
|
|||
const MESSAGE_TYPE_EMAIL = 'email';
|
||||
const MESSAGE_TYPE_SMS = 'sms';
|
||||
const MESSAGE_TYPE_PUSH = 'push';
|
||||
// API key types
|
||||
const API_KEY_STANDARD = 'standard';
|
||||
const API_KEY_DYNAMIC = 'dynamic';
|
||||
// Usage metrics
|
||||
const METRIC_TEAMS = 'teams';
|
||||
const METRIC_USERS = 'users';
|
||||
const METRIC_MESSAGES = 'messages';
|
||||
const METRIC_MESSAGES_COUNTRY_CODE = '{countryCode}.messages';
|
||||
|
||||
const METRIC_AUTH_METHOD_PHONE = 'auth.method.phone';
|
||||
const METRIC_AUTH_METHOD_PHONE_COUNTRY_CODE = METRIC_AUTH_METHOD_PHONE . '.{countryCode}';
|
||||
const METRIC_MESSAGES = 'messages';
|
||||
const METRIC_MESSAGES_SENT = METRIC_MESSAGES . '.sent';
|
||||
const METRIC_MESSAGES_FAILED = METRIC_MESSAGES . '.failed';
|
||||
const METRIC_MESSAGES_TYPE = METRIC_MESSAGES . '.{type}';
|
||||
const METRIC_MESSAGES_TYPE_SENT = METRIC_MESSAGES . '.{type}.sent';
|
||||
const METRIC_MESSAGES_TYPE_FAILED = METRIC_MESSAGES . '.{type}.failed';
|
||||
const METRIC_MESSAGES_TYPE_PROVIDER = METRIC_MESSAGES . '.{type}.{provider}';
|
||||
const METRIC_MESSAGES_TYPE_PROVIDER_SENT = METRIC_MESSAGES . '.{type}.{provider}.sent';
|
||||
const METRIC_MESSAGES_TYPE_PROVIDER_FAILED = METRIC_MESSAGES . '.{type}.{provider}.failed';
|
||||
const METRIC_SESSIONS = 'sessions';
|
||||
const METRIC_DATABASES = 'databases';
|
||||
const METRIC_COLLECTIONS = 'collections';
|
||||
const METRIC_DATABASES_STORAGE = 'databases.storage';
|
||||
const METRIC_DATABASE_ID_COLLECTIONS = '{databaseInternalId}.collections';
|
||||
const METRIC_DATABASE_ID_STORAGE = '{databaseInternalId}.databases.storage';
|
||||
const METRIC_DOCUMENTS = 'documents';
|
||||
const METRIC_DATABASE_ID_DOCUMENTS = '{databaseInternalId}.documents';
|
||||
const METRIC_DATABASE_ID_COLLECTION_ID_DOCUMENTS = '{databaseInternalId}.{collectionInternalId}.documents';
|
||||
const METRIC_DATABASE_ID_COLLECTION_ID_STORAGE = '{databaseInternalId}.{collectionInternalId}.databases.storage';
|
||||
const METRIC_BUCKETS = 'buckets';
|
||||
const METRIC_FILES = 'files';
|
||||
const METRIC_FILES_STORAGE = 'files.storage';
|
||||
|
|
@ -229,12 +258,20 @@ const METRIC_FUNCTIONS = 'functions';
|
|||
const METRIC_DEPLOYMENTS = 'deployments';
|
||||
const METRIC_DEPLOYMENTS_STORAGE = 'deployments.storage';
|
||||
const METRIC_BUILDS = 'builds';
|
||||
const METRIC_BUILDS_SUCCESS = 'builds.success';
|
||||
const METRIC_BUILDS_FAILED = 'builds.failed';
|
||||
const METRIC_BUILDS_STORAGE = 'builds.storage';
|
||||
const METRIC_BUILDS_COMPUTE = 'builds.compute';
|
||||
const METRIC_BUILDS_COMPUTE_SUCCESS = 'builds.compute.success';
|
||||
const METRIC_BUILDS_COMPUTE_FAILED = 'builds.compute.failed';
|
||||
const METRIC_BUILDS_MB_SECONDS = 'builds.mbSeconds';
|
||||
const METRIC_FUNCTION_ID_BUILDS = '{functionInternalId}.builds';
|
||||
const METRIC_FUNCTION_ID_BUILDS_SUCCESS = '{functionInternalId}.builds.success';
|
||||
const METRIC_FUNCTION_ID_BUILDS_FAILED = '{functionInternalId}.builds.failed';
|
||||
const METRIC_FUNCTION_ID_BUILDS_STORAGE = '{functionInternalId}.builds.storage';
|
||||
const METRIC_FUNCTION_ID_BUILDS_COMPUTE = '{functionInternalId}.builds.compute';
|
||||
const METRIC_FUNCTION_ID_BUILDS_COMPUTE_SUCCESS = '{functionInternalId}.builds.compute.success';
|
||||
const METRIC_FUNCTION_ID_BUILDS_COMPUTE_FAILED = '{functionInternalId}.builds.compute.failed';
|
||||
const METRIC_FUNCTION_ID_DEPLOYMENTS = '{resourceType}.{resourceInternalId}.deployments';
|
||||
const METRIC_FUNCTION_ID_DEPLOYMENTS_STORAGE = '{resourceType}.{resourceInternalId}.deployments.storage';
|
||||
const METRIC_FUNCTION_ID_BUILDS_MB_SECONDS = '{functionInternalId}.builds.mbSeconds';
|
||||
|
|
@ -293,6 +330,8 @@ Config::load('storage-logos', __DIR__ . '/config/storage/logos.php');
|
|||
Config::load('storage-mimes', __DIR__ . '/config/storage/mimes.php');
|
||||
Config::load('storage-inputs', __DIR__ . '/config/storage/inputs.php');
|
||||
Config::load('storage-outputs', __DIR__ . '/config/storage/outputs.php');
|
||||
Config::load('runtime-specifications', __DIR__ . '/config/runtimes/specifications.php');
|
||||
Config::load('function-templates', __DIR__ . '/config/function-templates.php');
|
||||
|
||||
/**
|
||||
* New DB Filters
|
||||
|
|
@ -347,8 +386,7 @@ Database::addFilter(
|
|||
if (isset($formatOptions['min']) || isset($formatOptions['max'])) {
|
||||
$attribute
|
||||
->setAttribute('min', $formatOptions['min'])
|
||||
->setAttribute('max', $formatOptions['max'])
|
||||
;
|
||||
->setAttribute('max', $formatOptions['max']);
|
||||
}
|
||||
|
||||
return $value;
|
||||
|
|
@ -731,6 +769,27 @@ $register->set('logger', function () {
|
|||
$providerName = System::getEnv('_APP_LOGGING_PROVIDER', '');
|
||||
$providerConfig = System::getEnv('_APP_LOGGING_CONFIG', '');
|
||||
|
||||
try {
|
||||
$loggingProvider = new DSN($providerConfig ?? '');
|
||||
|
||||
$providerName = $loggingProvider->getScheme();
|
||||
$providerConfig = match ($providerName) {
|
||||
'sentry' => ['key' => $loggingProvider->getPassword(), 'projectId' => $loggingProvider->getUser() ?? '', 'host' => 'https://' . $loggingProvider->getHost()],
|
||||
'logowl' => ['ticket' => $loggingProvider->getUser() ?? '', 'host' => $loggingProvider->getHost()],
|
||||
default => ['key' => $loggingProvider->getHost()],
|
||||
};
|
||||
} catch (Throwable $th) {
|
||||
// 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' => '',],
|
||||
'logowl' => ['ticket' => $configChunks[0] ?? '', 'host' => ''],
|
||||
default => ['key' => $providerConfig],
|
||||
};
|
||||
}
|
||||
|
||||
if (empty($providerName) || empty($providerConfig)) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -739,20 +798,26 @@ $register->set('logger', function () {
|
|||
throw new Exception(Exception::GENERAL_SERVER_ERROR, "Logging provider not supported. Logging is disabled");
|
||||
}
|
||||
|
||||
// Old Sentry Format conversion. Fallback until the old syntax is completely deprecated.
|
||||
if (str_contains($providerConfig, ';') && strtolower($providerName) == 'sentry') {
|
||||
$configChunks = \explode(";", $providerConfig);
|
||||
|
||||
$sentryKey = $configChunks[0];
|
||||
$projectId = $configChunks[1];
|
||||
|
||||
$providerConfig = 'https://' . $sentryKey . '@sentry.io/' . $projectId;
|
||||
try {
|
||||
$adapter = match ($providerName) {
|
||||
'sentry' => new Sentry($providerConfig['projectId'], $providerConfig['key'], $providerConfig['host']),
|
||||
'logowl' => new LogOwl($providerConfig['ticket'], $providerConfig['host']),
|
||||
'raygun' => new Raygun($providerConfig['key']),
|
||||
'appsignal' => new AppSignal($providerConfig['key']),
|
||||
default => null
|
||||
};
|
||||
} catch (Throwable $th) {
|
||||
$adapter = null;
|
||||
}
|
||||
|
||||
if ($adapter === null) {
|
||||
Console::error("Logging provider not supported. Logging is disabled");
|
||||
return;
|
||||
}
|
||||
|
||||
$classname = '\\Utopia\\Logger\\Adapter\\' . \ucfirst($providerName);
|
||||
$adapter = new $classname($providerConfig);
|
||||
return new Logger($adapter);
|
||||
});
|
||||
|
||||
$register->set('pools', function () {
|
||||
$group = new Group();
|
||||
|
||||
|
|
@ -972,7 +1037,7 @@ $register->set('smtp', function () {
|
|||
return $mail;
|
||||
});
|
||||
$register->set('geodb', function () {
|
||||
return new Reader(__DIR__ . '/assets/dbip/dbip-country-lite-2024-02.mmdb');
|
||||
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');
|
||||
|
|
@ -1196,18 +1261,18 @@ App::setResource('user', function ($mode, $project, $console, $request, $respons
|
|||
$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([]);
|
||||
}
|
||||
}
|
||||
// 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', 900, 10); // Instantiate with key, algo, maxAge and leeway.
|
||||
$jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 3600, 0);
|
||||
|
||||
try {
|
||||
$payload = $jwt->decode($authJWT);
|
||||
|
|
@ -1216,14 +1281,15 @@ App::setResource('user', function ($mode, $project, $console, $request, $respons
|
|||
}
|
||||
|
||||
$jwtUserId = $payload['userId'] ?? '';
|
||||
$jwtSessionId = $payload['sessionId'] ?? '';
|
||||
|
||||
if ($jwtUserId && $jwtSessionId) {
|
||||
if (!empty($jwtUserId)) {
|
||||
$user = $dbForProject->getDocument('users', $jwtUserId);
|
||||
}
|
||||
|
||||
if (empty($user->find('$id', $jwtSessionId, 'sessions'))) { // Match JWT to active token
|
||||
$user = new Document([]);
|
||||
$jwtSessionId = $payload['sessionId'] ?? '';
|
||||
if (!empty($jwtSessionId)) {
|
||||
if (empty($user->find('$id', $jwtSessionId, 'sessions'))) { // Match JWT to active token
|
||||
$user = new Document([]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1278,7 +1344,7 @@ App::setResource('console', function () {
|
|||
'$collection' => ID::custom('projects'),
|
||||
'description' => 'Appwrite core engine',
|
||||
'logo' => '',
|
||||
'teamId' => -1,
|
||||
'teamId' => null,
|
||||
'webhooks' => [],
|
||||
'keys' => [],
|
||||
'platforms' => [
|
||||
|
|
@ -1296,9 +1362,11 @@ App::setResource('console', function () {
|
|||
'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)) : [],
|
||||
|
|
@ -1461,9 +1529,9 @@ App::setResource('deviceForBuilds', function ($project) {
|
|||
return getDevice(APP_STORAGE_BUILDS . '/app-' . $project->getId());
|
||||
}, ['project']);
|
||||
|
||||
function getDevice($root): Device
|
||||
function getDevice(string $root, string $connection = ''): Device
|
||||
{
|
||||
$connection = System::getEnv('_APP_CONNECTIONS_STORAGE', '');
|
||||
$connection = !empty($connection) ? $connection : System::getEnv('_APP_CONNECTIONS_STORAGE', '');
|
||||
|
||||
if (!empty($connection)) {
|
||||
$acl = 'private';
|
||||
|
|
@ -1708,3 +1776,35 @@ App::setResource('requestTimestamp', function ($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]),
|
||||
]);
|
||||
});
|
||||
|
||||
if (!$team) {
|
||||
$team = new Document([]);
|
||||
}
|
||||
return $team;
|
||||
}, ['project', 'dbForConsole', 'utopia', 'request']);
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ require_once __DIR__ . '/init.php';
|
|||
Runtime::enableCoroutine(SWOOLE_HOOK_ALL);
|
||||
|
||||
// Allows overriding
|
||||
if (!function_exists("getConsoleDB")) {
|
||||
if (!function_exists('getConsoleDB')) {
|
||||
function getConsoleDB(): Database
|
||||
{
|
||||
global $register;
|
||||
|
|
@ -66,7 +66,7 @@ if (!function_exists("getConsoleDB")) {
|
|||
}
|
||||
|
||||
// Allows overriding
|
||||
if (!function_exists("getProjectDB")) {
|
||||
if (!function_exists('getProjectDB')) {
|
||||
function getProjectDB(Document $project): Database
|
||||
{
|
||||
global $register;
|
||||
|
|
@ -115,7 +115,7 @@ if (!function_exists("getProjectDB")) {
|
|||
}
|
||||
|
||||
// Allows overriding
|
||||
if (!function_exists("getCache")) {
|
||||
if (!function_exists('getCache')) {
|
||||
function getCache(): Cache
|
||||
{
|
||||
global $register;
|
||||
|
|
@ -137,7 +137,14 @@ if (!function_exists("getCache")) {
|
|||
}
|
||||
}
|
||||
|
||||
$realtime = new Realtime();
|
||||
if (!function_exists('getRealtime')) {
|
||||
function getRealtime(): Realtime
|
||||
{
|
||||
return new Realtime();
|
||||
}
|
||||
}
|
||||
|
||||
$realtime = getRealtime();
|
||||
|
||||
/**
|
||||
* Table for statistics across all workers.
|
||||
|
|
@ -186,8 +193,12 @@ $logError = function (Throwable $error, string $action) use ($register) {
|
|||
$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));
|
||||
|
|
|
|||
|
|
@ -75,6 +75,7 @@ $image = $this->getParam('image', '');
|
|||
- _APP_LOCALE
|
||||
- _APP_CONSOLE_WHITELIST_ROOT
|
||||
- _APP_CONSOLE_WHITELIST_EMAILS
|
||||
- _APP_CONSOLE_SESSION_ALERTS
|
||||
- _APP_CONSOLE_WHITELIST_IPS
|
||||
- _APP_CONSOLE_HOSTNAMES
|
||||
- _APP_SYSTEM_EMAIL_NAME
|
||||
|
|
@ -138,7 +139,6 @@ $image = $this->getParam('image', '');
|
|||
- _APP_FUNCTIONS_RUNTIMES
|
||||
- _APP_EXECUTOR_SECRET
|
||||
- _APP_EXECUTOR_HOST
|
||||
- _APP_LOGGING_PROVIDER
|
||||
- _APP_LOGGING_CONFIG
|
||||
- _APP_MAINTENANCE_INTERVAL
|
||||
- _APP_MAINTENANCE_DELAY
|
||||
|
|
@ -163,6 +163,28 @@ $image = $this->getParam('image', '');
|
|||
- _APP_MIGRATIONS_FIREBASE_CLIENT_SECRET
|
||||
- _APP_ASSISTANT_OPENAI_API_KEY
|
||||
|
||||
appwrite-console:
|
||||
<<: *x-logging
|
||||
container_name: appwrite-console
|
||||
image: <?php echo $organization; ?>/console:5.0.12
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- appwrite
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.constraint-label-stack=appwrite"
|
||||
- "traefik.docker.network=appwrite"
|
||||
- "traefik.http.services.appwrite_console.loadbalancer.server.port=80"
|
||||
#ws
|
||||
- traefik.http.routers.appwrite_console_http.entrypoints=appwrite_web
|
||||
- traefik.http.routers.appwrite_console_http.rule=PathPrefix(`/console`)
|
||||
- traefik.http.routers.appwrite_console_http.service=appwrite_console
|
||||
# wss
|
||||
- traefik.http.routers.appwrite_console_https.entrypoints=appwrite_websecure
|
||||
- traefik.http.routers.appwrite_console_https.rule=PathPrefix(`/console`)
|
||||
- traefik.http.routers.appwrite_console_https.service=appwrite_console
|
||||
- traefik.http.routers.appwrite_console_https.tls=true
|
||||
|
||||
appwrite-realtime:
|
||||
image: <?php echo $organization; ?>/<?php echo $image; ?>:<?php echo $version."\n"; ?>
|
||||
entrypoint: realtime
|
||||
|
|
@ -204,7 +226,6 @@ $image = $this->getParam('image', '');
|
|||
- _APP_DB_USER
|
||||
- _APP_DB_PASS
|
||||
- _APP_USAGE_STATS
|
||||
- _APP_LOGGING_PROVIDER
|
||||
- _APP_LOGGING_CONFIG
|
||||
|
||||
appwrite-worker-audits:
|
||||
|
|
@ -231,7 +252,6 @@ $image = $this->getParam('image', '');
|
|||
- _APP_DB_SCHEMA
|
||||
- _APP_DB_USER
|
||||
- _APP_DB_PASS
|
||||
- _APP_LOGGING_PROVIDER
|
||||
- _APP_LOGGING_CONFIG
|
||||
|
||||
appwrite-worker-webhooks:
|
||||
|
|
@ -260,7 +280,6 @@ $image = $this->getParam('image', '');
|
|||
- _APP_REDIS_PORT
|
||||
- _APP_REDIS_USER
|
||||
- _APP_REDIS_PASS
|
||||
- _APP_LOGGING_PROVIDER
|
||||
- _APP_LOGGING_CONFIG
|
||||
|
||||
appwrite-worker-deletes:
|
||||
|
|
@ -314,10 +333,12 @@ $image = $this->getParam('image', '');
|
|||
- _APP_STORAGE_WASABI_SECRET
|
||||
- _APP_STORAGE_WASABI_REGION
|
||||
- _APP_STORAGE_WASABI_BUCKET
|
||||
- _APP_LOGGING_PROVIDER
|
||||
- _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: <?php echo $organization; ?>/<?php echo $image; ?>:<?php echo $version."\n"; ?>
|
||||
|
|
@ -343,7 +364,6 @@ $image = $this->getParam('image', '');
|
|||
- _APP_DB_SCHEMA
|
||||
- _APP_DB_USER
|
||||
- _APP_DB_PASS
|
||||
- _APP_LOGGING_PROVIDER
|
||||
- _APP_LOGGING_CONFIG
|
||||
|
||||
appwrite-worker-builds:
|
||||
|
|
@ -375,7 +395,6 @@ $image = $this->getParam('image', '');
|
|||
- _APP_DB_SCHEMA
|
||||
- _APP_DB_USER
|
||||
- _APP_DB_PASS
|
||||
- _APP_LOGGING_PROVIDER
|
||||
- _APP_LOGGING_CONFIG
|
||||
- _APP_VCS_GITHUB_APP_NAME
|
||||
- _APP_VCS_GITHUB_PRIVATE_KEY
|
||||
|
|
@ -441,7 +460,6 @@ $image = $this->getParam('image', '');
|
|||
- _APP_DB_SCHEMA
|
||||
- _APP_DB_USER
|
||||
- _APP_DB_PASS
|
||||
- _APP_LOGGING_PROVIDER
|
||||
- _APP_LOGGING_CONFIG
|
||||
|
||||
appwrite-worker-functions:
|
||||
|
|
@ -460,6 +478,8 @@ $image = $this->getParam('image', '');
|
|||
- _APP_ENV
|
||||
- _APP_WORKER_PER_CORE
|
||||
- _APP_OPENSSL_KEY_V1
|
||||
- _APP_DOMAIN
|
||||
- _APP_OPTIONS_FORCE_HTTPS
|
||||
- _APP_REDIS_HOST
|
||||
- _APP_REDIS_PORT
|
||||
- _APP_REDIS_USER
|
||||
|
|
@ -479,7 +499,6 @@ $image = $this->getParam('image', '');
|
|||
- _APP_DOCKER_HUB_USERNAME
|
||||
- _APP_DOCKER_HUB_PASSWORD
|
||||
- _APP_LOGGING_CONFIG
|
||||
- _APP_LOGGING_PROVIDER
|
||||
|
||||
appwrite-worker-mails:
|
||||
image: <?php echo $organization; ?>/<?php echo $image; ?>:<?php echo $version."\n"; ?>
|
||||
|
|
@ -511,8 +530,9 @@ $image = $this->getParam('image', '');
|
|||
- _APP_SMTP_SECURE
|
||||
- _APP_SMTP_USERNAME
|
||||
- _APP_SMTP_PASSWORD
|
||||
- _APP_LOGGING_PROVIDER
|
||||
- _APP_LOGGING_CONFIG
|
||||
- _APP_DOMAIN
|
||||
- _APP_OPTIONS_FORCE_HTTPS
|
||||
|
||||
appwrite-worker-messaging:
|
||||
image: <?php echo $organization; ?>/<?php echo $image; ?>:<?php echo $version."\n"; ?>
|
||||
|
|
@ -539,7 +559,6 @@ $image = $this->getParam('image', '');
|
|||
- _APP_DB_SCHEMA
|
||||
- _APP_DB_USER
|
||||
- _APP_DB_PASS
|
||||
- _APP_LOGGING_PROVIDER
|
||||
- _APP_LOGGING_CONFIG
|
||||
- _APP_SMS_FROM
|
||||
- _APP_SMS_PROVIDER
|
||||
|
|
@ -591,7 +610,6 @@ $image = $this->getParam('image', '');
|
|||
- _APP_DB_SCHEMA
|
||||
- _APP_DB_USER
|
||||
- _APP_DB_PASS
|
||||
- _APP_LOGGING_PROVIDER
|
||||
- _APP_LOGGING_CONFIG
|
||||
- _APP_MIGRATIONS_FIREBASE_CLIENT_ID
|
||||
- _APP_MIGRATIONS_FIREBASE_CLIENT_SECRET
|
||||
|
|
@ -655,7 +673,6 @@ $image = $this->getParam('image', '');
|
|||
- _APP_REDIS_USER
|
||||
- _APP_REDIS_PASS
|
||||
- _APP_USAGE_STATS
|
||||
- _APP_LOGGING_PROVIDER
|
||||
- _APP_LOGGING_CONFIG
|
||||
- _APP_USAGE_AGGREGATION_INTERVAL
|
||||
|
||||
|
|
@ -664,6 +681,7 @@ $image = $this->getParam('image', '');
|
|||
entrypoint: worker-usage-dump
|
||||
<<: *x-logging
|
||||
container_name: appwrite-worker-usage-dump
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- appwrite
|
||||
depends_on:
|
||||
|
|
@ -683,7 +701,6 @@ $image = $this->getParam('image', '');
|
|||
- _APP_REDIS_USER
|
||||
- _APP_REDIS_PASS
|
||||
- _APP_USAGE_STATS
|
||||
- _APP_LOGGING_PROVIDER
|
||||
- _APP_LOGGING_CONFIG
|
||||
- _APP_USAGE_AGGREGATION_INTERVAL
|
||||
|
||||
|
|
@ -712,6 +729,31 @@ $image = $this->getParam('image', '');
|
|||
- _APP_DB_USER
|
||||
- _APP_DB_PASS
|
||||
|
||||
appwrite-task-scheduler-executions:
|
||||
image: <?php echo $organization; ?>/<?php echo $image; ?>:<?php echo $version."\n"; ?>
|
||||
entrypoint: schedule-executions
|
||||
container_name: appwrite-task-scheduler-executions
|
||||
<<: *x-logging
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- appwrite
|
||||
depends_on:
|
||||
- mariadb
|
||||
- redis
|
||||
environment:
|
||||
- _APP_ENV
|
||||
- _APP_WORKER_PER_CORE
|
||||
- _APP_OPENSSL_KEY_V1
|
||||
- _APP_REDIS_HOST
|
||||
- _APP_REDIS_PORT
|
||||
- _APP_REDIS_USER
|
||||
- _APP_REDIS_PASS
|
||||
- _APP_DB_HOST
|
||||
- _APP_DB_PORT
|
||||
- _APP_DB_SCHEMA
|
||||
- _APP_DB_USER
|
||||
- _APP_DB_PASS
|
||||
|
||||
appwrite-task-scheduler-messages:
|
||||
image: <?php echo $organization; ?>/<?php echo $image; ?>:<?php echo $version."\n"; ?>
|
||||
entrypoint: schedule-messages
|
||||
|
|
@ -753,7 +795,7 @@ $image = $this->getParam('image', '');
|
|||
<<: *x-logging
|
||||
restart: unless-stopped
|
||||
stop_signal: SIGINT
|
||||
image: openruntimes/executor:0.5.7
|
||||
image: openruntimes/executor:0.6.11
|
||||
networks:
|
||||
- appwrite
|
||||
- runtimes
|
||||
|
|
@ -773,7 +815,6 @@ $image = $this->getParam('image', '');
|
|||
- OPR_EXECUTOR_ENV=$_APP_ENV
|
||||
- OPR_EXECUTOR_RUNTIMES=$_APP_FUNCTIONS_RUNTIMES
|
||||
- OPR_EXECUTOR_SECRET=$_APP_EXECUTOR_SECRET
|
||||
- OPR_EXECUTOR_LOGGING_PROVIDER=$_APP_LOGGING_PROVIDER
|
||||
- OPR_EXECUTOR_LOGGING_CONFIG=$_APP_LOGGING_CONFIG
|
||||
- OPR_EXECUTOR_STORAGE_DEVICE=$_APP_STORAGE_DEVICE
|
||||
- OPR_EXECUTOR_STORAGE_S3_ACCESS_KEY=$_APP_STORAGE_S3_ACCESS_KEY
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ Server::setResource('project', function (Message $message, Database $dbForConsol
|
|||
$payload = $message->getPayload() ?? [];
|
||||
$project = new Document($payload['project'] ?? []);
|
||||
|
||||
if ($project->getId() === 'console') {
|
||||
if ($project->getId() === 'console' || $project->isEmpty() || ! empty($project->getInternalId())) {
|
||||
return $project;
|
||||
}
|
||||
|
||||
|
|
@ -352,8 +352,12 @@ $worker
|
|||
$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));
|
||||
|
|
|
|||
3
bin/schedule-executions
Normal file
3
bin/schedule-executions
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
#!/bin/sh
|
||||
|
||||
php /usr/src/code/app/cli.php schedule-executions $@
|
||||
|
|
@ -13,7 +13,8 @@
|
|||
"scripts": {
|
||||
"test": "vendor/bin/phpunit",
|
||||
"lint": "vendor/bin/pint --test",
|
||||
"format": "vendor/bin/pint"
|
||||
"format": "vendor/bin/pint",
|
||||
"bench": "vendor/bin/phpbench run --report=benchmark"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
|
@ -42,7 +43,7 @@
|
|||
"ext-openssl": "*",
|
||||
"ext-zlib": "*",
|
||||
"ext-sockets": "*",
|
||||
"appwrite/php-runtimes": "0.13.*",
|
||||
"appwrite/php-runtimes": "0.16.*",
|
||||
"appwrite/php-clamav": "2.0.*",
|
||||
"utopia-php/abuse": "0.43.*",
|
||||
"utopia-php/analytics": "0.10.*",
|
||||
|
|
@ -50,16 +51,16 @@
|
|||
"utopia-php/cache": "0.10.*",
|
||||
"utopia-php/cli": "0.15.*",
|
||||
"utopia-php/config": "0.2.*",
|
||||
"utopia-php/database": "dev-feat-migrations as 0.53.4",
|
||||
"utopia-php/database": "dev-feat-migrations as 0.53.6",
|
||||
"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.5.*",
|
||||
"utopia-php/logger": "0.6.*",
|
||||
"utopia-php/messaging": "0.12.*",
|
||||
"utopia-php/migration": "0.5.*",
|
||||
"utopia-php/migration": "0.6.*",
|
||||
"utopia-php/orchestration": "0.9.*",
|
||||
"utopia-php/platform": "0.7.*",
|
||||
"utopia-php/pools": "0.5.*",
|
||||
|
|
@ -68,7 +69,7 @@
|
|||
"utopia-php/registry": "0.5.*",
|
||||
"utopia-php/storage": "0.18.*",
|
||||
"utopia-php/swoole": "0.8.*",
|
||||
"utopia-php/system": "0.8.*",
|
||||
"utopia-php/system": "0.9.*",
|
||||
"utopia-php/vcs": "0.8.*",
|
||||
"utopia-php/websocket": "0.1.*",
|
||||
"matomo/device-detector": "6.1.*",
|
||||
|
|
@ -86,7 +87,8 @@
|
|||
"phpunit/phpunit": "9.5.20",
|
||||
"swoole/ide-helper": "5.1.2",
|
||||
"textalk/websocket": "1.5.7",
|
||||
"laravel/pint": "^1.14"
|
||||
"laravel/pint": "^1.14",
|
||||
"phpbench/phpbench": "^1.2"
|
||||
},
|
||||
"provide": {
|
||||
"ext-phpiredis": "*"
|
||||
|
|
|
|||
1442
composer.lock
generated
1442
composer.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -40,6 +40,7 @@ services:
|
|||
networks:
|
||||
- gateway
|
||||
- appwrite
|
||||
- runtimes
|
||||
|
||||
appwrite:
|
||||
container_name: appwrite
|
||||
|
|
@ -51,7 +52,7 @@ services:
|
|||
DEBUG: false
|
||||
TESTING: true
|
||||
VERSION: dev
|
||||
ports:
|
||||
ports:
|
||||
- 9501:80
|
||||
networks:
|
||||
- appwrite
|
||||
|
|
@ -97,10 +98,12 @@ services:
|
|||
- _APP_LOCALE
|
||||
- _APP_CONSOLE_WHITELIST_ROOT
|
||||
- _APP_CONSOLE_WHITELIST_EMAILS
|
||||
- _APP_CONSOLE_SESSION_ALERTS
|
||||
- _APP_CONSOLE_WHITELIST_IPS
|
||||
- _APP_CONSOLE_HOSTNAMES
|
||||
- _APP_SYSTEM_EMAIL_NAME
|
||||
- _APP_SYSTEM_EMAIL_ADDRESS
|
||||
- _APP_SYSTEM_TEAM_EMAIL
|
||||
- _APP_EMAIL_SECURITY
|
||||
- _APP_SYSTEM_RESPONSE_FORMAT
|
||||
- _APP_OPTIONS_ABUSE
|
||||
|
|
@ -160,7 +163,6 @@ services:
|
|||
- _APP_FUNCTIONS_RUNTIMES
|
||||
- _APP_EXECUTOR_SECRET
|
||||
- _APP_EXECUTOR_HOST
|
||||
- _APP_LOGGING_PROVIDER
|
||||
- _APP_LOGGING_CONFIG
|
||||
- _APP_MAINTENANCE_INTERVAL
|
||||
- _APP_MAINTENANCE_RETENTION_EXECUTION
|
||||
|
|
@ -191,6 +193,28 @@ services:
|
|||
- _APP_EXPERIMENT_LOGGING_CONFIG
|
||||
- _APP_DATABASE_SHARED_TABLES
|
||||
|
||||
appwrite-console:
|
||||
<<: *x-logging
|
||||
container_name: appwrite-console
|
||||
image: appwrite/console:5.0.12
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- appwrite
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.constraint-label-stack=appwrite"
|
||||
- "traefik.docker.network=appwrite"
|
||||
- "traefik.http.services.appwrite_console.loadbalancer.server.port=80"
|
||||
#ws
|
||||
- traefik.http.routers.appwrite_console_http.entrypoints=appwrite_web
|
||||
- traefik.http.routers.appwrite_console_http.rule=PathPrefix(`/console`)
|
||||
- traefik.http.routers.appwrite_console_http.service=appwrite_console
|
||||
# wss
|
||||
- traefik.http.routers.appwrite_console_https.entrypoints=appwrite_websecure
|
||||
- traefik.http.routers.appwrite_console_https.rule=PathPrefix(`/console`)
|
||||
- traefik.http.routers.appwrite_console_https.service=appwrite_console
|
||||
- traefik.http.routers.appwrite_console_https.tls=true
|
||||
|
||||
appwrite-realtime:
|
||||
entrypoint: realtime
|
||||
<<: *x-logging
|
||||
|
|
@ -237,7 +261,6 @@ services:
|
|||
- _APP_DB_USER
|
||||
- _APP_DB_PASS
|
||||
- _APP_USAGE_STATS
|
||||
- _APP_LOGGING_PROVIDER
|
||||
- _APP_LOGGING_CONFIG
|
||||
- _APP_DATABASE_SHARED_TABLES
|
||||
|
||||
|
|
@ -267,7 +290,6 @@ services:
|
|||
- _APP_DB_SCHEMA
|
||||
- _APP_DB_USER
|
||||
- _APP_DB_PASS
|
||||
- _APP_LOGGING_PROVIDER
|
||||
- _APP_LOGGING_CONFIG
|
||||
- _APP_DATABASE_SHARED_TABLES
|
||||
|
||||
|
|
@ -299,7 +321,6 @@ services:
|
|||
- _APP_REDIS_PORT
|
||||
- _APP_REDIS_USER
|
||||
- _APP_REDIS_PASS
|
||||
- _APP_LOGGING_PROVIDER
|
||||
- _APP_LOGGING_CONFIG
|
||||
- _APP_WEBHOOK_MAX_FAILED_ATTEMPTS
|
||||
- _APP_DATABASE_SHARED_TABLES
|
||||
|
|
@ -356,7 +377,6 @@ services:
|
|||
- _APP_STORAGE_WASABI_SECRET
|
||||
- _APP_STORAGE_WASABI_REGION
|
||||
- _APP_STORAGE_WASABI_BUCKET
|
||||
- _APP_LOGGING_PROVIDER
|
||||
- _APP_LOGGING_CONFIG
|
||||
- _APP_EXECUTOR_SECRET
|
||||
- _APP_EXECUTOR_HOST
|
||||
|
|
@ -388,7 +408,6 @@ services:
|
|||
- _APP_DB_SCHEMA
|
||||
- _APP_DB_USER
|
||||
- _APP_DB_PASS
|
||||
- _APP_LOGGING_PROVIDER
|
||||
- _APP_LOGGING_CONFIG
|
||||
- _APP_WORKERS_NUM
|
||||
- _APP_QUEUE_NAME
|
||||
|
|
@ -424,7 +443,6 @@ services:
|
|||
- _APP_DB_SCHEMA
|
||||
- _APP_DB_USER
|
||||
- _APP_DB_PASS
|
||||
- _APP_LOGGING_PROVIDER
|
||||
- _APP_LOGGING_CONFIG
|
||||
- _APP_VCS_GITHUB_APP_NAME
|
||||
- _APP_VCS_GITHUB_PRIVATE_KEY
|
||||
|
|
@ -492,7 +510,6 @@ services:
|
|||
- _APP_DB_SCHEMA
|
||||
- _APP_DB_USER
|
||||
- _APP_DB_PASS
|
||||
- _APP_LOGGING_PROVIDER
|
||||
- _APP_LOGGING_CONFIG
|
||||
- _APP_DATABASE_SHARED_TABLES
|
||||
|
||||
|
|
@ -514,6 +531,8 @@ services:
|
|||
- _APP_ENV
|
||||
- _APP_WORKER_PER_CORE
|
||||
- _APP_OPENSSL_KEY_V1
|
||||
- _APP_DOMAIN
|
||||
- _APP_OPTIONS_FORCE_HTTPS
|
||||
- _APP_REDIS_HOST
|
||||
- _APP_REDIS_PORT
|
||||
- _APP_REDIS_USER
|
||||
|
|
@ -565,7 +584,6 @@ services:
|
|||
- _APP_SMTP_SECURE
|
||||
- _APP_SMTP_USERNAME
|
||||
- _APP_SMTP_PASSWORD
|
||||
- _APP_LOGGING_PROVIDER
|
||||
- _APP_LOGGING_CONFIG
|
||||
- _APP_DOMAIN
|
||||
- _APP_OPTIONS_FORCE_HTTPS
|
||||
|
|
@ -598,7 +616,6 @@ services:
|
|||
- _APP_DB_SCHEMA
|
||||
- _APP_DB_USER
|
||||
- _APP_DB_PASS
|
||||
- _APP_LOGGING_PROVIDER
|
||||
- _APP_LOGGING_CONFIG
|
||||
- _APP_SMS_FROM
|
||||
- _APP_SMS_PROVIDER
|
||||
|
|
@ -656,7 +673,6 @@ services:
|
|||
- _APP_DB_SCHEMA
|
||||
- _APP_DB_USER
|
||||
- _APP_DB_PASS
|
||||
- _APP_LOGGING_PROVIDER
|
||||
- _APP_LOGGING_CONFIG
|
||||
- _APP_MIGRATIONS_FIREBASE_CLIENT_ID
|
||||
- _APP_MIGRATIONS_FIREBASE_CLIENT_SECRET
|
||||
|
|
@ -727,7 +743,6 @@ services:
|
|||
- _APP_REDIS_USER
|
||||
- _APP_REDIS_PASS
|
||||
- _APP_USAGE_STATS
|
||||
- _APP_LOGGING_PROVIDER
|
||||
- _APP_LOGGING_CONFIG
|
||||
- _APP_USAGE_AGGREGATION_INTERVAL
|
||||
- _APP_DATABASE_SHARED_TABLES
|
||||
|
|
@ -759,7 +774,6 @@ services:
|
|||
- _APP_REDIS_USER
|
||||
- _APP_REDIS_PASS
|
||||
- _APP_USAGE_STATS
|
||||
- _APP_LOGGING_PROVIDER
|
||||
- _APP_LOGGING_CONFIG
|
||||
- _APP_USAGE_AGGREGATION_INTERVAL
|
||||
- _APP_DATABASE_SHARED_TABLES
|
||||
|
|
@ -792,6 +806,33 @@ services:
|
|||
- _APP_DB_PASS
|
||||
- _APP_DATABASE_SHARED_TABLES
|
||||
|
||||
appwrite-task-scheduler-executions:
|
||||
entrypoint: schedule-executions
|
||||
<<: *x-logging
|
||||
container_name: appwrite-task-scheduler-executions
|
||||
image: appwrite-dev
|
||||
networks:
|
||||
- appwrite
|
||||
volumes:
|
||||
- ./app:/usr/src/code/app
|
||||
- ./src:/usr/src/code/src
|
||||
depends_on:
|
||||
- mariadb
|
||||
- redis
|
||||
environment:
|
||||
- _APP_ENV
|
||||
- _APP_WORKER_PER_CORE
|
||||
- _APP_OPENSSL_KEY_V1
|
||||
- _APP_REDIS_HOST
|
||||
- _APP_REDIS_PORT
|
||||
- _APP_REDIS_USER
|
||||
- _APP_REDIS_PASS
|
||||
- _APP_DB_HOST
|
||||
- _APP_DB_PORT
|
||||
- _APP_DB_SCHEMA
|
||||
- _APP_DB_USER
|
||||
- _APP_DB_PASS
|
||||
|
||||
appwrite-task-scheduler-messages:
|
||||
entrypoint: schedule-messages
|
||||
<<: *x-logging
|
||||
|
|
@ -833,7 +874,7 @@ services:
|
|||
hostname: exc1
|
||||
<<: *x-logging
|
||||
stop_signal: SIGINT
|
||||
image: openruntimes/executor:0.5.7
|
||||
image: openruntimes/executor:0.6.11
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- appwrite
|
||||
|
|
@ -854,8 +895,7 @@ services:
|
|||
- OPR_EXECUTOR_ENV=$_APP_ENV
|
||||
- OPR_EXECUTOR_RUNTIMES=$_APP_FUNCTIONS_RUNTIMES
|
||||
- OPR_EXECUTOR_SECRET=$_APP_EXECUTOR_SECRET
|
||||
- OPR_EXECUTOR_RUNTIME_VERSIONS=v2,v3
|
||||
- OPR_EXECUTOR_LOGGING_PROVIDER=$_APP_LOGGING_PROVIDER
|
||||
- OPR_EXECUTOR_RUNTIME_VERSIONS=v2,v4
|
||||
- OPR_EXECUTOR_LOGGING_CONFIG=$_APP_LOGGING_CONFIG
|
||||
- OPR_EXECUTOR_STORAGE_DEVICE=$_APP_STORAGE_DEVICE
|
||||
- OPR_EXECUTOR_STORAGE_S3_ACCESS_KEY=$_APP_STORAGE_S3_ACCESS_KEY
|
||||
|
|
@ -884,7 +924,7 @@ services:
|
|||
hostname: proxy
|
||||
<<: *x-logging
|
||||
stop_signal: SIGINT
|
||||
image: openruntimes/proxy:0.3.1
|
||||
image: openruntimes/proxy:0.5.5
|
||||
networks:
|
||||
- appwrite
|
||||
- runtimes
|
||||
|
|
@ -893,7 +933,6 @@ services:
|
|||
- OPR_PROXY_ENV=$_APP_ENV
|
||||
- OPR_PROXY_EXECUTOR_SECRET=$_APP_EXECUTOR_SECRET
|
||||
- OPR_PROXY_SECRET=$_APP_EXECUTOR_SECRET
|
||||
- OPR_PROXY_LOGGING_PROVIDER=$_APP_LOGGING_PROVIDER
|
||||
- OPR_PROXY_LOGGING_CONFIG=$_APP_LOGGING_CONFIG
|
||||
- OPR_PROXY_ALGORITHM=random
|
||||
- OPR_PROXY_EXECUTORS=exc1
|
||||
|
|
|
|||
|
|
@ -8,4 +8,4 @@ X-Appwrite-JWT: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ...
|
|||
|
||||
{
|
||||
"otp": "<OTP>"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
import io.appwrite.Client;
|
||||
import io.appwrite.coroutines.CoroutineCallback;
|
||||
import io.appwrite.services.Account;
|
||||
|
||||
Client client = new Client(context)
|
||||
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
|
||||
.setProject("<YOUR_PROJECT_ID>"); // Your project ID
|
||||
|
||||
Account account = new Account(client);
|
||||
|
||||
account.createAnonymousSession(new CoroutineCallback<>((result, error) -> {
|
||||
if (error != null) {
|
||||
error.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
Log.d("Appwrite", result.toString());
|
||||
}));
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
import io.appwrite.Client;
|
||||
import io.appwrite.coroutines.CoroutineCallback;
|
||||
import io.appwrite.services.Account;
|
||||
|
||||
Client client = new Client(context)
|
||||
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
|
||||
.setProject("<YOUR_PROJECT_ID>"); // Your project ID
|
||||
|
||||
Account account = new Account(client);
|
||||
|
||||
account.createEmailPasswordSession(
|
||||
"email@example.com", // email
|
||||
"password", // password
|
||||
new CoroutineCallback<>((result, error) -> {
|
||||
if (error != null) {
|
||||
error.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
Log.d("Appwrite", result.toString());
|
||||
})
|
||||
);
|
||||
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
import io.appwrite.Client;
|
||||
import io.appwrite.coroutines.CoroutineCallback;
|
||||
import io.appwrite.services.Account;
|
||||
|
||||
Client client = new Client(context)
|
||||
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
|
||||
.setProject("<YOUR_PROJECT_ID>"); // Your project ID
|
||||
|
||||
Account account = new Account(client);
|
||||
|
||||
account.createEmailToken(
|
||||
"<USER_ID>", // userId
|
||||
"email@example.com", // email
|
||||
false, // phrase (optional)
|
||||
new CoroutineCallback<>((result, error) -> {
|
||||
if (error != null) {
|
||||
error.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
Log.d("Appwrite", result.toString());
|
||||
})
|
||||
);
|
||||
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
import io.appwrite.Client;
|
||||
import io.appwrite.coroutines.CoroutineCallback;
|
||||
import io.appwrite.services.Account;
|
||||
|
||||
Client client = new Client(context)
|
||||
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
|
||||
.setProject("<YOUR_PROJECT_ID>"); // Your project ID
|
||||
|
||||
Account account = new Account(client);
|
||||
|
||||
account.createJWT(new CoroutineCallback<>((result, error) -> {
|
||||
if (error != null) {
|
||||
error.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
Log.d("Appwrite", result.toString());
|
||||
}));
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
import io.appwrite.Client;
|
||||
import io.appwrite.coroutines.CoroutineCallback;
|
||||
import io.appwrite.services.Account;
|
||||
|
||||
Client client = new Client(context)
|
||||
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
|
||||
.setProject("<YOUR_PROJECT_ID>"); // Your project ID
|
||||
|
||||
Account account = new Account(client);
|
||||
|
||||
account.createMagicURLToken(
|
||||
"<USER_ID>", // userId
|
||||
"email@example.com", // email
|
||||
"https://example.com", // url (optional)
|
||||
false, // phrase (optional)
|
||||
new CoroutineCallback<>((result, error) -> {
|
||||
if (error != null) {
|
||||
error.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
Log.d("Appwrite", result.toString());
|
||||
})
|
||||
);
|
||||
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
import io.appwrite.Client;
|
||||
import io.appwrite.coroutines.CoroutineCallback;
|
||||
import io.appwrite.services.Account;
|
||||
import io.appwrite.enums.AuthenticatorType;
|
||||
|
||||
Client client = new Client(context)
|
||||
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
|
||||
.setProject("<YOUR_PROJECT_ID>"); // Your project ID
|
||||
|
||||
Account account = new Account(client);
|
||||
|
||||
account.createMfaAuthenticator(
|
||||
AuthenticatorType.TOTP, // type
|
||||
new CoroutineCallback<>((result, error) -> {
|
||||
if (error != null) {
|
||||
error.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
Log.d("Appwrite", result.toString());
|
||||
})
|
||||
);
|
||||
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
import io.appwrite.Client;
|
||||
import io.appwrite.coroutines.CoroutineCallback;
|
||||
import io.appwrite.services.Account;
|
||||
import io.appwrite.enums.AuthenticationFactor;
|
||||
|
||||
Client client = new Client(context)
|
||||
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
|
||||
.setProject("<YOUR_PROJECT_ID>"); // Your project ID
|
||||
|
||||
Account account = new Account(client);
|
||||
|
||||
account.createMfaChallenge(
|
||||
AuthenticationFactor.EMAIL, // factor
|
||||
new CoroutineCallback<>((result, error) -> {
|
||||
if (error != null) {
|
||||
error.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
Log.d("Appwrite", result.toString());
|
||||
})
|
||||
);
|
||||
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
import io.appwrite.Client;
|
||||
import io.appwrite.coroutines.CoroutineCallback;
|
||||
import io.appwrite.services.Account;
|
||||
|
||||
Client client = new Client(context)
|
||||
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
|
||||
.setProject("<YOUR_PROJECT_ID>"); // Your project ID
|
||||
|
||||
Account account = new Account(client);
|
||||
|
||||
account.createMfaRecoveryCodes(new CoroutineCallback<>((result, error) -> {
|
||||
if (error != null) {
|
||||
error.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
Log.d("Appwrite", result.toString());
|
||||
}));
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
import io.appwrite.Client;
|
||||
import io.appwrite.coroutines.CoroutineCallback;
|
||||
import io.appwrite.services.Account;
|
||||
import io.appwrite.enums.OAuthProvider;
|
||||
|
||||
Client client = new Client(context)
|
||||
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
|
||||
.setProject("<YOUR_PROJECT_ID>"); // Your project ID
|
||||
|
||||
Account account = new Account(client);
|
||||
|
||||
account.createOAuth2Session(
|
||||
OAuthProvider.AMAZON, // provider
|
||||
"https://example.com", // success (optional)
|
||||
"https://example.com", // failure (optional)
|
||||
listOf(), // scopes (optional)
|
||||
new CoroutineCallback<>((result, error) -> {
|
||||
if (error != null) {
|
||||
error.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
Log.d("Appwrite", result.toString());
|
||||
})
|
||||
);
|
||||
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
import io.appwrite.Client;
|
||||
import io.appwrite.coroutines.CoroutineCallback;
|
||||
import io.appwrite.services.Account;
|
||||
import io.appwrite.enums.OAuthProvider;
|
||||
|
||||
Client client = new Client(context)
|
||||
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
|
||||
.setProject("<YOUR_PROJECT_ID>"); // Your project ID
|
||||
|
||||
Account account = new Account(client);
|
||||
|
||||
account.createOAuth2Token(
|
||||
OAuthProvider.AMAZON, // provider
|
||||
"https://example.com", // success (optional)
|
||||
"https://example.com", // failure (optional)
|
||||
listOf(), // scopes (optional)
|
||||
new CoroutineCallback<>((result, error) -> {
|
||||
if (error != null) {
|
||||
error.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
Log.d("Appwrite", result.toString());
|
||||
})
|
||||
);
|
||||
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
import io.appwrite.Client;
|
||||
import io.appwrite.coroutines.CoroutineCallback;
|
||||
import io.appwrite.services.Account;
|
||||
|
||||
Client client = new Client(context)
|
||||
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
|
||||
.setProject("<YOUR_PROJECT_ID>"); // Your project ID
|
||||
|
||||
Account account = new Account(client);
|
||||
|
||||
account.createPhoneToken(
|
||||
"<USER_ID>", // userId
|
||||
"+12065550100", // phone
|
||||
new CoroutineCallback<>((result, error) -> {
|
||||
if (error != null) {
|
||||
error.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
Log.d("Appwrite", result.toString());
|
||||
})
|
||||
);
|
||||
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
import io.appwrite.Client;
|
||||
import io.appwrite.coroutines.CoroutineCallback;
|
||||
import io.appwrite.services.Account;
|
||||
|
||||
Client client = new Client(context)
|
||||
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
|
||||
.setProject("<YOUR_PROJECT_ID>"); // Your project ID
|
||||
|
||||
Account account = new Account(client);
|
||||
|
||||
account.createPhoneVerification(new CoroutineCallback<>((result, error) -> {
|
||||
if (error != null) {
|
||||
error.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
Log.d("Appwrite", result.toString());
|
||||
}));
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
import io.appwrite.Client;
|
||||
import io.appwrite.coroutines.CoroutineCallback;
|
||||
import io.appwrite.services.Account;
|
||||
|
||||
Client client = new Client(context)
|
||||
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
|
||||
.setProject("<YOUR_PROJECT_ID>"); // Your project ID
|
||||
|
||||
Account account = new Account(client);
|
||||
|
||||
account.createPushTarget(
|
||||
"<TARGET_ID>", // targetId
|
||||
"<IDENTIFIER>", // identifier
|
||||
"<PROVIDER_ID>", // providerId (optional)
|
||||
new CoroutineCallback<>((result, error) -> {
|
||||
if (error != null) {
|
||||
error.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
Log.d("Appwrite", result.toString());
|
||||
})
|
||||
);
|
||||
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
import io.appwrite.Client;
|
||||
import io.appwrite.coroutines.CoroutineCallback;
|
||||
import io.appwrite.services.Account;
|
||||
|
||||
Client client = new Client(context)
|
||||
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
|
||||
.setProject("<YOUR_PROJECT_ID>"); // Your project ID
|
||||
|
||||
Account account = new Account(client);
|
||||
|
||||
account.createRecovery(
|
||||
"email@example.com", // email
|
||||
"https://example.com", // url
|
||||
new CoroutineCallback<>((result, error) -> {
|
||||
if (error != null) {
|
||||
error.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
Log.d("Appwrite", result.toString());
|
||||
})
|
||||
);
|
||||
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
import io.appwrite.Client;
|
||||
import io.appwrite.coroutines.CoroutineCallback;
|
||||
import io.appwrite.services.Account;
|
||||
|
||||
Client client = new Client(context)
|
||||
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
|
||||
.setProject("<YOUR_PROJECT_ID>"); // Your project ID
|
||||
|
||||
Account account = new Account(client);
|
||||
|
||||
account.createSession(
|
||||
"<USER_ID>", // userId
|
||||
"<SECRET>", // secret
|
||||
new CoroutineCallback<>((result, error) -> {
|
||||
if (error != null) {
|
||||
error.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
Log.d("Appwrite", result.toString());
|
||||
})
|
||||
);
|
||||
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
import io.appwrite.Client;
|
||||
import io.appwrite.coroutines.CoroutineCallback;
|
||||
import io.appwrite.services.Account;
|
||||
|
||||
Client client = new Client(context)
|
||||
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
|
||||
.setProject("<YOUR_PROJECT_ID>"); // Your project ID
|
||||
|
||||
Account account = new Account(client);
|
||||
|
||||
account.createVerification(
|
||||
"https://example.com", // url
|
||||
new CoroutineCallback<>((result, error) -> {
|
||||
if (error != null) {
|
||||
error.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
Log.d("Appwrite", result.toString());
|
||||
})
|
||||
);
|
||||
|
||||
25
docs/examples/1.6.x/client-android/java/account/create.md
Normal file
25
docs/examples/1.6.x/client-android/java/account/create.md
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import io.appwrite.Client;
|
||||
import io.appwrite.coroutines.CoroutineCallback;
|
||||
import io.appwrite.services.Account;
|
||||
|
||||
Client client = new Client(context)
|
||||
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
|
||||
.setProject("<YOUR_PROJECT_ID>"); // Your project ID
|
||||
|
||||
Account account = new Account(client);
|
||||
|
||||
account.create(
|
||||
"<USER_ID>", // userId
|
||||
"email@example.com", // email
|
||||
"", // password
|
||||
"<NAME>", // name (optional)
|
||||
new CoroutineCallback<>((result, error) -> {
|
||||
if (error != null) {
|
||||
error.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
Log.d("Appwrite", result.toString());
|
||||
})
|
||||
);
|
||||
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
import io.appwrite.Client;
|
||||
import io.appwrite.coroutines.CoroutineCallback;
|
||||
import io.appwrite.services.Account;
|
||||
|
||||
Client client = new Client(context)
|
||||
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
|
||||
.setProject("<YOUR_PROJECT_ID>"); // Your project ID
|
||||
|
||||
Account account = new Account(client);
|
||||
|
||||
account.deleteIdentity(
|
||||
"<IDENTITY_ID>", // identityId
|
||||
new CoroutineCallback<>((result, error) -> {
|
||||
if (error != null) {
|
||||
error.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
Log.d("Appwrite", result.toString());
|
||||
})
|
||||
);
|
||||
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
import io.appwrite.Client;
|
||||
import io.appwrite.coroutines.CoroutineCallback;
|
||||
import io.appwrite.services.Account;
|
||||
import io.appwrite.enums.AuthenticatorType;
|
||||
|
||||
Client client = new Client(context)
|
||||
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
|
||||
.setProject("<YOUR_PROJECT_ID>"); // Your project ID
|
||||
|
||||
Account account = new Account(client);
|
||||
|
||||
account.deleteMfaAuthenticator(
|
||||
AuthenticatorType.TOTP, // type
|
||||
new CoroutineCallback<>((result, error) -> {
|
||||
if (error != null) {
|
||||
error.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
Log.d("Appwrite", result.toString());
|
||||
})
|
||||
);
|
||||
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
import io.appwrite.Client;
|
||||
import io.appwrite.coroutines.CoroutineCallback;
|
||||
import io.appwrite.services.Account;
|
||||
|
||||
Client client = new Client(context)
|
||||
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
|
||||
.setProject("<YOUR_PROJECT_ID>"); // Your project ID
|
||||
|
||||
Account account = new Account(client);
|
||||
|
||||
account.deletePushTarget(
|
||||
"<TARGET_ID>", // targetId
|
||||
new CoroutineCallback<>((result, error) -> {
|
||||
if (error != null) {
|
||||
error.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
Log.d("Appwrite", result.toString());
|
||||
})
|
||||
);
|
||||
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
import io.appwrite.Client;
|
||||
import io.appwrite.coroutines.CoroutineCallback;
|
||||
import io.appwrite.services.Account;
|
||||
|
||||
Client client = new Client(context)
|
||||
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
|
||||
.setProject("<YOUR_PROJECT_ID>"); // Your project ID
|
||||
|
||||
Account account = new Account(client);
|
||||
|
||||
account.deleteSession(
|
||||
"<SESSION_ID>", // sessionId
|
||||
new CoroutineCallback<>((result, error) -> {
|
||||
if (error != null) {
|
||||
error.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
Log.d("Appwrite", result.toString());
|
||||
})
|
||||
);
|
||||
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
import io.appwrite.Client;
|
||||
import io.appwrite.coroutines.CoroutineCallback;
|
||||
import io.appwrite.services.Account;
|
||||
|
||||
Client client = new Client(context)
|
||||
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
|
||||
.setProject("<YOUR_PROJECT_ID>"); // Your project ID
|
||||
|
||||
Account account = new Account(client);
|
||||
|
||||
account.deleteSessions(new CoroutineCallback<>((result, error) -> {
|
||||
if (error != null) {
|
||||
error.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
Log.d("Appwrite", result.toString());
|
||||
}));
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
import io.appwrite.Client;
|
||||
import io.appwrite.coroutines.CoroutineCallback;
|
||||
import io.appwrite.services.Account;
|
||||
|
||||
Client client = new Client(context)
|
||||
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
|
||||
.setProject("<YOUR_PROJECT_ID>"); // Your project ID
|
||||
|
||||
Account account = new Account(client);
|
||||
|
||||
account.getMfaRecoveryCodes(new CoroutineCallback<>((result, error) -> {
|
||||
if (error != null) {
|
||||
error.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
Log.d("Appwrite", result.toString());
|
||||
}));
|
||||
18
docs/examples/1.6.x/client-android/java/account/get-prefs.md
Normal file
18
docs/examples/1.6.x/client-android/java/account/get-prefs.md
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import io.appwrite.Client;
|
||||
import io.appwrite.coroutines.CoroutineCallback;
|
||||
import io.appwrite.services.Account;
|
||||
|
||||
Client client = new Client(context)
|
||||
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
|
||||
.setProject("<YOUR_PROJECT_ID>"); // Your project ID
|
||||
|
||||
Account account = new Account(client);
|
||||
|
||||
account.getPrefs(new CoroutineCallback<>((result, error) -> {
|
||||
if (error != null) {
|
||||
error.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
Log.d("Appwrite", result.toString());
|
||||
}));
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
import io.appwrite.Client;
|
||||
import io.appwrite.coroutines.CoroutineCallback;
|
||||
import io.appwrite.services.Account;
|
||||
|
||||
Client client = new Client(context)
|
||||
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
|
||||
.setProject("<YOUR_PROJECT_ID>"); // Your project ID
|
||||
|
||||
Account account = new Account(client);
|
||||
|
||||
account.getSession(
|
||||
"<SESSION_ID>", // sessionId
|
||||
new CoroutineCallback<>((result, error) -> {
|
||||
if (error != null) {
|
||||
error.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
Log.d("Appwrite", result.toString());
|
||||
})
|
||||
);
|
||||
|
||||
18
docs/examples/1.6.x/client-android/java/account/get.md
Normal file
18
docs/examples/1.6.x/client-android/java/account/get.md
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import io.appwrite.Client;
|
||||
import io.appwrite.coroutines.CoroutineCallback;
|
||||
import io.appwrite.services.Account;
|
||||
|
||||
Client client = new Client(context)
|
||||
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
|
||||
.setProject("<YOUR_PROJECT_ID>"); // Your project ID
|
||||
|
||||
Account account = new Account(client);
|
||||
|
||||
account.get(new CoroutineCallback<>((result, error) -> {
|
||||
if (error != null) {
|
||||
error.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
Log.d("Appwrite", result.toString());
|
||||
}));
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
import io.appwrite.Client;
|
||||
import io.appwrite.coroutines.CoroutineCallback;
|
||||
import io.appwrite.services.Account;
|
||||
|
||||
Client client = new Client(context)
|
||||
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
|
||||
.setProject("<YOUR_PROJECT_ID>"); // Your project ID
|
||||
|
||||
Account account = new Account(client);
|
||||
|
||||
account.listIdentities(
|
||||
listOf(), // queries (optional)
|
||||
new CoroutineCallback<>((result, error) -> {
|
||||
if (error != null) {
|
||||
error.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
Log.d("Appwrite", result.toString());
|
||||
})
|
||||
);
|
||||
|
||||
22
docs/examples/1.6.x/client-android/java/account/list-logs.md
Normal file
22
docs/examples/1.6.x/client-android/java/account/list-logs.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import io.appwrite.Client;
|
||||
import io.appwrite.coroutines.CoroutineCallback;
|
||||
import io.appwrite.services.Account;
|
||||
|
||||
Client client = new Client(context)
|
||||
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
|
||||
.setProject("<YOUR_PROJECT_ID>"); // Your project ID
|
||||
|
||||
Account account = new Account(client);
|
||||
|
||||
account.listLogs(
|
||||
listOf(), // queries (optional)
|
||||
new CoroutineCallback<>((result, error) -> {
|
||||
if (error != null) {
|
||||
error.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
Log.d("Appwrite", result.toString());
|
||||
})
|
||||
);
|
||||
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
import io.appwrite.Client;
|
||||
import io.appwrite.coroutines.CoroutineCallback;
|
||||
import io.appwrite.services.Account;
|
||||
|
||||
Client client = new Client(context)
|
||||
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
|
||||
.setProject("<YOUR_PROJECT_ID>"); // Your project ID
|
||||
|
||||
Account account = new Account(client);
|
||||
|
||||
account.listMfaFactors(new CoroutineCallback<>((result, error) -> {
|
||||
if (error != null) {
|
||||
error.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
Log.d("Appwrite", result.toString());
|
||||
}));
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
import io.appwrite.Client;
|
||||
import io.appwrite.coroutines.CoroutineCallback;
|
||||
import io.appwrite.services.Account;
|
||||
|
||||
Client client = new Client(context)
|
||||
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
|
||||
.setProject("<YOUR_PROJECT_ID>"); // Your project ID
|
||||
|
||||
Account account = new Account(client);
|
||||
|
||||
account.listSessions(new CoroutineCallback<>((result, error) -> {
|
||||
if (error != null) {
|
||||
error.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
Log.d("Appwrite", result.toString());
|
||||
}));
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
import io.appwrite.Client;
|
||||
import io.appwrite.coroutines.CoroutineCallback;
|
||||
import io.appwrite.services.Account;
|
||||
|
||||
Client client = new Client(context)
|
||||
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
|
||||
.setProject("<YOUR_PROJECT_ID>"); // Your project ID
|
||||
|
||||
Account account = new Account(client);
|
||||
|
||||
account.updateEmail(
|
||||
"email@example.com", // email
|
||||
"password", // password
|
||||
new CoroutineCallback<>((result, error) -> {
|
||||
if (error != null) {
|
||||
error.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
Log.d("Appwrite", result.toString());
|
||||
})
|
||||
);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue