mirror of
https://github.com/appwrite/appwrite
synced 2026-05-23 08:58:35 +00:00
commit
e406dead9a
5118 changed files with 392330 additions and 1069 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
|
||||
|
|
|
|||
78
.github/workflows/tests.yml
vendored
78
.github/workflows/tests.yml
vendored
|
|
@ -148,3 +148,81 @@ jobs:
|
|||
|
||||
- name: Run ${{matrix.service}} Shared Tables Tests
|
||||
run: _APP_DATABASE_SHARED_TABLES=database_db_main docker compose exec -T appwrite test /usr/src/code/tests/e2e/Services/${{matrix.service}} --debug
|
||||
|
||||
benchamrking:
|
||||
name: Benchmark
|
||||
runs-on: ubuntu-latest
|
||||
needs: setup
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
- name: Load Cache
|
||||
uses: actions/cache@v3
|
||||
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
|
||||
|
|
|
|||
|
|
@ -553,6 +553,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
|
|
@ -1,4 +1,4 @@
|
|||
FROM composer:2.0 as composer
|
||||
FROM composer:2.0 AS composer
|
||||
|
||||
ARG TESTING=false
|
||||
ENV TESTING=$TESTING
|
||||
|
|
@ -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"
|
||||
|
||||
|
|
@ -48,7 +31,6 @@ RUN \
|
|||
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 +61,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 的本机主机上完成安装后,服务器可能需要几分钟才能启动。
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
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.
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -3029,7 +3029,7 @@ $projectCollections = array_merge([
|
|||
'size' => 8,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => 'v3',
|
||||
'default' => 'v4',
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
|
|
@ -3054,7 +3054,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 +3859,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 +3922,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,
|
||||
|
|
@ -4311,6 +4387,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,
|
||||
|
|
@ -4518,6 +4605,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 +4688,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 +5783,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 => [
|
||||
|
|
|
|||
2068
app/config/function-templates.php
Normal file
2068
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",
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ return [
|
|||
[
|
||||
'key' => 'web',
|
||||
'name' => 'Web',
|
||||
'version' => '15.0.0',
|
||||
'version' => '16.0.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-web',
|
||||
'package' => 'https://www.npmjs.com/package/appwrite',
|
||||
'enabled' => true,
|
||||
|
|
@ -63,7 +63,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 +81,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 +116,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 +138,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 +203,7 @@ return [
|
|||
[
|
||||
'key' => 'web',
|
||||
'name' => 'Console',
|
||||
'version' => '0.6.3',
|
||||
'version' => '1.1.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-console',
|
||||
'package' => '',
|
||||
'enabled' => true,
|
||||
|
|
@ -214,14 +214,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 +249,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 +267,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 +285,7 @@ return [
|
|||
[
|
||||
'key' => 'php',
|
||||
'name' => 'PHP',
|
||||
'version' => '11.0.2',
|
||||
'version' => '12.0.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-php',
|
||||
'package' => 'https://packagist.org/packages/appwrite/appwrite',
|
||||
'enabled' => true,
|
||||
|
|
@ -303,7 +303,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 +321,7 @@ return [
|
|||
[
|
||||
'key' => 'ruby',
|
||||
'name' => 'Ruby',
|
||||
'version' => '11.0.2',
|
||||
'version' => '12.1.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-ruby',
|
||||
'package' => 'https://rubygems.org/gems/appwrite',
|
||||
'enabled' => true,
|
||||
|
|
@ -339,10 +339,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 +354,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.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-dotnet',
|
||||
'package' => 'https://www.nuget.org/packages/Appwrite',
|
||||
'enabled' => true,
|
||||
|
|
@ -393,7 +375,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 +393,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 +415,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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
]
|
||||
];
|
||||
9900
app/config/specs/open-api3-1.6.x-client.json
Normal file
9900
app/config/specs/open-api3-1.6.x-client.json
Normal file
File diff suppressed because it is too large
Load diff
38445
app/config/specs/open-api3-1.6.x-console.json
Normal file
38445
app/config/specs/open-api3-1.6.x-console.json
Normal file
File diff suppressed because it is too large
Load diff
26997
app/config/specs/open-api3-1.6.x-server.json
Normal file
26997
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
10055
app/config/specs/swagger2-1.6.x-client.json
Normal file
10055
app/config/specs/swagger2-1.6.x-client.json
Normal file
File diff suppressed because it is too large
Load diff
38969
app/config/specs/swagger2-1.6.x-console.json
Normal file
38969
app/config/specs/swagger2-1.6.x-console.json
Normal file
File diff suppressed because it is too large
Load diff
27447
app/config/specs/swagger2-1.6.x-server.json
Normal file
27447
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
|
|
@ -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);
|
||||
|
||||
|
|
@ -3825,7 +3966,7 @@ App::get('/v1/account/mfa/recovery-codes')
|
|||
|
||||
App::delete('/v1/account/mfa/authenticators/:type')
|
||||
->desc('Delete Authenticator')
|
||||
->groups(['api', 'account'])
|
||||
->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());
|
||||
|
||||
|
|
@ -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')
|
||||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -20,12 +20,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;
|
||||
|
|
@ -228,13 +231,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));
|
||||
|
||||
|
|
@ -280,6 +285,10 @@ function updateAttribute(
|
|||
->setAttribute('default', $default)
|
||||
->setAttribute('required', $required);
|
||||
|
||||
if (!empty($size)) {
|
||||
$attribute->setAttribute('size', $size);
|
||||
}
|
||||
|
||||
$formatOptions = $attribute->getAttribute('formatOptions');
|
||||
|
||||
switch ($attribute->getAttribute('format')) {
|
||||
|
|
@ -345,6 +354,7 @@ function updateAttribute(
|
|||
$dbForProject->updateRelationship(
|
||||
collection: $collectionId,
|
||||
id: $key,
|
||||
newKey: $newKey,
|
||||
onDelete: $primaryDocumentOptions['onDelete'],
|
||||
);
|
||||
|
||||
|
|
@ -352,22 +362,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
|
||||
|
|
@ -1152,6 +1192,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);
|
||||
|
|
@ -1859,10 +1900,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,
|
||||
|
|
@ -1871,8 +1914,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
|
||||
|
|
@ -1898,10 +1943,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,
|
||||
|
|
@ -1911,7 +1957,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
|
||||
|
|
@ -1938,10 +1985,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,
|
||||
|
|
@ -1952,7 +2000,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
|
||||
|
|
@ -1978,10 +2027,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,
|
||||
|
|
@ -1991,7 +2041,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
|
||||
|
|
@ -2017,10 +2068,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,
|
||||
|
|
@ -2030,7 +2082,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
|
||||
|
|
@ -2058,10 +2111,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,
|
||||
|
|
@ -2072,7 +2126,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', []);
|
||||
|
|
@ -2107,10 +2162,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,
|
||||
|
|
@ -2121,7 +2177,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', []);
|
||||
|
|
@ -2154,10 +2211,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,
|
||||
|
|
@ -2166,7 +2224,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
|
||||
|
|
@ -2192,10 +2251,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,
|
||||
|
|
@ -2204,7 +2264,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
|
||||
|
|
@ -2229,6 +2290,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')
|
||||
|
|
@ -2237,6 +2299,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/:key/
|
|||
string $collectionId,
|
||||
string $key,
|
||||
?string $onDelete,
|
||||
?string $newKey,
|
||||
Response $response,
|
||||
Database $dbForProject,
|
||||
Event $queueForEvents
|
||||
|
|
@ -2251,7 +2314,8 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/:key/
|
|||
required: false,
|
||||
options: [
|
||||
'onDelete' => $onDelete
|
||||
]
|
||||
],
|
||||
newKey: $newKey
|
||||
);
|
||||
|
||||
$options = $attribute->getAttribute('options', []);
|
||||
|
|
|
|||
|
|
@ -10,7 +10,11 @@ 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\Payload;
|
||||
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 +37,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 +47,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 +158,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 +167,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 +183,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 +198,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 +233,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 +244,7 @@ App::post('/v1/functions')
|
|||
'providerBranch' => $providerBranch,
|
||||
'providerRootDirectory' => $providerRootDirectory,
|
||||
'providerSilentMode' => $providerSilentMode,
|
||||
'specification' => $specification
|
||||
]));
|
||||
|
||||
$schedule = Authorization::skip(
|
||||
|
|
@ -277,9 +293,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 +468,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 +483,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 +771,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 +791,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 +828,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 +890,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 +917,7 @@ App::put('/v1/functions/:functionId')
|
|||
'logging' => $logging,
|
||||
'entrypoint' => $entrypoint,
|
||||
'commands' => $commands,
|
||||
'scopes' => $scopes,
|
||||
'installationId' => $installation->getId(),
|
||||
'installationInternalId' => $installation->getInternalId(),
|
||||
'providerRepositoryId' => $providerRepositoryId,
|
||||
|
|
@ -827,6 +926,7 @@ App::put('/v1/functions/:functionId')
|
|||
'providerBranch' => $providerBranch,
|
||||
'providerRootDirectory' => $providerRootDirectory,
|
||||
'providerSilentMode' => $providerSilentMode,
|
||||
'specification' => $specification,
|
||||
'search' => implode(' ', [$functionId, $name, $runtime]),
|
||||
])));
|
||||
|
||||
|
|
@ -852,10 +952,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 +1152,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 +1172,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 +1272,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 +1310,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 +1343,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 +1428,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 +1475,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 +1545,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 +1556,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 +1614,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 +1713,54 @@ 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('body', '', new Payload(10485760, 0), 'HTTP body of execution. Default value is empty string.', true)
|
||||
->param('async', false, new Boolean(), 'Execute code in the background. Default value is false.', true)
|
||||
->param('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, bool $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) {
|
||||
|
||||
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);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -47,7 +47,8 @@ App::get('/v1/project/usage')
|
|||
METRIC_USERS,
|
||||
METRIC_BUCKETS,
|
||||
METRIC_FILES_STORAGE,
|
||||
METRIC_DEPLOYMENTS_STORAGE
|
||||
METRIC_DEPLOYMENTS_STORAGE,
|
||||
METRIC_BUILDS_STORAGE
|
||||
],
|
||||
'period' => [
|
||||
METRIC_NETWORK_REQUESTS,
|
||||
|
|
@ -133,6 +134,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,19 +182,27 @@ App::get('/v1/project/usage')
|
|||
];
|
||||
}, $dbForProject->find('buckets'));
|
||||
|
||||
$deploymentsStorageBreakdown = array_map(function ($function) use ($dbForProject) {
|
||||
$functionsStorageBreakdown = 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);
|
||||
$value = $dbForProject->findOne('stats', [
|
||||
Query::equal('metric', [$metric]),
|
||||
$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['value'] ?? 0,
|
||||
'value' => $value,
|
||||
];
|
||||
}, $dbForProject->find('functions'));
|
||||
|
||||
|
|
@ -231,12 +272,16 @@ App::get('/v1/project/usage')
|
|||
'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,
|
||||
'executionsMbSecondsBreakdown' => $executionsMbSecondsBreakdown,
|
||||
'buildsMbSecondsBreakdown' => $buildsMbSecondsBreakdown,
|
||||
'functionsStorageBreakdown' => $functionsStorageBreakdown,
|
||||
]), Response::MODEL_USAGE_PROJECT);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -168,6 +174,7 @@ App::post('/v1/projects')
|
|||
'webhooks' => null,
|
||||
'keys' => null,
|
||||
'auths' => $auths,
|
||||
'accessedAt' => DateTime::now(),
|
||||
'search' => implode(' ', [$projectId, $name]),
|
||||
'database' => $dsn,
|
||||
]));
|
||||
|
|
@ -362,7 +369,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])
|
||||
|
|
@ -603,6 +610,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'])
|
||||
|
|
@ -823,6 +861,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'])
|
||||
|
|
@ -1157,7 +1234,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);
|
||||
|
|
@ -1318,6 +1395,41 @@ 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')
|
||||
|
|
@ -1332,7 +1444,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)
|
||||
|
|
|
|||
|
|
@ -1309,7 +1309,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 +1320,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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -338,10 +339,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 +356,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())
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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];
|
||||
|
||||
|
|
@ -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);
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -29,6 +32,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 +97,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 +137,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 +171,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 +258,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 +310,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 +324,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 +346,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 +382,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 +483,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 +602,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 +777,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());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -904,7 +958,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 +983,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 {
|
||||
|
|
|
|||
|
|
@ -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', 'projects.write')
|
||||
->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;
|
||||
|
|
@ -195,56 +197,100 @@ App::init()
|
|||
$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', '');
|
||||
$apiKey = $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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -721,12 +767,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);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -57,8 +57,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) {
|
||||
|
|
|
|||
113
app/init.php
113
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;
|
||||
|
|
@ -62,6 +63,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 +114,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 +148,9 @@ 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;
|
||||
|
||||
// Database Reconnect
|
||||
const DATABASE_RECONNECT_SLEEP = 2;
|
||||
|
|
@ -167,7 +176,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,11 +217,24 @@ 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';
|
||||
|
|
@ -229,12 +251,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';
|
||||
|
|
@ -291,6 +321,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
|
||||
|
|
@ -345,8 +377,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;
|
||||
|
|
@ -729,6 +760,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;
|
||||
}
|
||||
|
|
@ -737,20 +789,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();
|
||||
|
||||
|
|
@ -970,7 +1028,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');
|
||||
|
|
@ -1205,7 +1263,7 @@ App::setResource('user', function ($mode, $project, $console, $request, $respons
|
|||
$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);
|
||||
|
|
@ -1214,14 +1272,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([]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1294,9 +1353,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)) : [],
|
||||
|
|
|
|||
|
|
@ -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,7 +530,6 @@ $image = $this->getParam('image', '');
|
|||
- _APP_SMTP_SECURE
|
||||
- _APP_SMTP_USERNAME
|
||||
- _APP_SMTP_PASSWORD
|
||||
- _APP_LOGGING_PROVIDER
|
||||
- _APP_LOGGING_CONFIG
|
||||
|
||||
appwrite-worker-messaging:
|
||||
|
|
@ -539,7 +557,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 +608,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 +671,6 @@ $image = $this->getParam('image', '');
|
|||
- _APP_REDIS_USER
|
||||
- _APP_REDIS_PASS
|
||||
- _APP_USAGE_STATS
|
||||
- _APP_LOGGING_PROVIDER
|
||||
- _APP_LOGGING_CONFIG
|
||||
- _APP_USAGE_AGGREGATION_INTERVAL
|
||||
|
||||
|
|
@ -684,7 +699,6 @@ $image = $this->getParam('image', '');
|
|||
- _APP_REDIS_USER
|
||||
- _APP_REDIS_PASS
|
||||
- _APP_USAGE_STATS
|
||||
- _APP_LOGGING_PROVIDER
|
||||
- _APP_LOGGING_CONFIG
|
||||
- _APP_USAGE_AGGREGATION_INTERVAL
|
||||
|
||||
|
|
@ -713,6 +727,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
|
||||
|
|
@ -754,7 +793,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
|
||||
|
|
@ -774,7 +813,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
|
||||
|
|
|
|||
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,22 +43,22 @@
|
|||
"ext-openssl": "*",
|
||||
"ext-zlib": "*",
|
||||
"ext-sockets": "*",
|
||||
"appwrite/php-runtimes": "0.13.*",
|
||||
"appwrite/php-runtimes": "0.15.*",
|
||||
"appwrite/php-clamav": "2.0.*",
|
||||
"utopia-php/abuse": "0.42.*",
|
||||
"utopia-php/abuse": "0.43.0",
|
||||
"utopia-php/analytics": "0.10.*",
|
||||
"utopia-php/audit": "0.42.*",
|
||||
"utopia-php/audit": "0.43.0",
|
||||
"utopia-php/cache": "0.10.*",
|
||||
"utopia-php/cli": "0.15.*",
|
||||
"utopia-php/config": "0.2.*",
|
||||
"utopia-php/database": "0.52.*",
|
||||
"utopia-php/database": "0.53.*",
|
||||
"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/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/orchestration": "0.9.*",
|
||||
|
|
@ -82,11 +83,12 @@
|
|||
},
|
||||
"require-dev": {
|
||||
"ext-fileinfo": "*",
|
||||
"appwrite/sdk-generator": "0.38.*",
|
||||
"appwrite/sdk-generator": "0.39.*",
|
||||
"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": "*"
|
||||
|
|
|
|||
1684
composer.lock
generated
1684
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());
|
||||
})
|
||||
);
|
||||
|
||||
|
|
@ -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.updateMFA(
|
||||
false, // mfa
|
||||
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.updateMagicURLSession(
|
||||
"<USER_ID>", // userId
|
||||
"<SECRET>", // secret
|
||||
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;
|
||||
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.updateMfaAuthenticator(
|
||||
AuthenticatorType.TOTP, // type
|
||||
"<OTP>", // otp
|
||||
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.updateMfaChallenge(
|
||||
"<CHALLENGE_ID>", // challengeId
|
||||
"<OTP>", // otp
|
||||
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.updateMfaRecoveryCodes(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.updateName(
|
||||
"<NAME>", // name
|
||||
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.updatePassword(
|
||||
"", // password
|
||||
"password", // oldPassword (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.updatePhoneSession(
|
||||
"<USER_ID>", // userId
|
||||
"<SECRET>", // secret
|
||||
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.updatePhoneVerification(
|
||||
"<USER_ID>", // userId
|
||||
"<SECRET>", // secret
|
||||
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.updatePhone(
|
||||
"+12065550100", // phone
|
||||
"password", // password
|
||||
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.updatePrefs(
|
||||
mapOf( "a" to "b" ), // prefs
|
||||
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.updatePushTarget(
|
||||
"<TARGET_ID>", // targetId
|
||||
"<IDENTIFIER>", // identifier
|
||||
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